In 8 Schritten um einen Workflow Prozess zu bauen

Camunda + Spring Boot ein kleines einfaches Beispiel

Erwartete Lesezeit: 7 minuten

Bei der Entwicklung eines prozessgesteuerten Mikroservices, habe ich mir verschiedene Prozessengines angesehen und die Entwicklungsvariante mit Camunda gefiel mir recht gut. Ich war überrascht ,  wie einfach es war.

Hier ein kleines Beispiel für einen Prozess, der prüft ob meine Website online ist. Zur Anzeige habe ich hier einen Human Task eingebaut , in dem man das Ergebnis sehen wird.

 

Schritt 1: Anlage eines einfachen Maven Projektes

Als Dependencies benötigen wir zum einen Spring Boot  und einmal Camunda

Hier die fertige POM.xml

<project xmlns="http://maven.apache.org/POM/4.0.0"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>de.oderkerk.bpm</groupId>
  <artifactId>testprocess</artifactId>
  <version>0.0.1-SNAPSHOT</version>
  <name>Testapp combining spring boot and camunda</name>
  <properties>
    <build.plugins.plugin.version>2.0.5.RELEASE</build.plugins.plugin.version>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
    <java.version>1.8</java.version>
  </properties>
  <dependencyManagement>
    <dependencies>
      <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-dependencies</artifactId>
        <version>2.0.5.RELEASE</version>
        <type>pom</type>
        <scope>import</scope>
      </dependency>
    </dependencies>
  </dependencyManagement>

  <dependencies>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-data-jpa</artifactId>
    </dependency>
    <dependency>
      <groupId>org.camunda.bpm.springboot</groupId>
      <artifactId>camunda-bpm-spring-boot-starter-webapp</artifactId>
      <version>3.0.0</version>
    </dependency>
    
    <dependency>
      <groupId>com.h2database</groupId>
      <artifactId>h2</artifactId>
      <scope>runtime</scope>
    </dependency>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
    </dependency>
  </dependencies>
  <build>
    <plugins>
      <plugin>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-maven-plugin</artifactId>
        <version>${build.plugins.plugin.version}</version>
        <configuration>
          <layout>ZIP</layout>
        </configuration>
        <executions>
          <execution>
            <goals>
              <goal>repackage</goal>
            </goals>
          </execution>
        </executions>
      </plugin>
    </plugins>
  </build>

</project>

 

 

Schritt 2: Hinterlegung einer Datenbank

Für dieses Beispiel habe ich eine lokale H2 Datenbank gewählt und diese in der Application.properties Datei konfiguriert :

# Datasource
spring.datasource.url=jdbc:h2:file:./target/db
spring.datasource.username=sa
spring.datasource.password=
spring.datasource.driver-class-name=org.h2.Driver
spring.jpa.database-platform=org.hibernate.dialect.H2Dialect

Um während des Tests mal in die Datenbank schauen zu können , habe ich zusätzlich die H2 Console ebenfalls durch einen Eintrag in der Application.properties Datei aktiviert. (Kein Muss)

# H2
spring.h2.console.enabled=true
spring.h2.console.path=/h2

 

Schritt 3: Automatische Anlage Admin User

Um nach dem Start der Anwendung gleich nutzen zu können, legen wir einen Admin User an, der beim ersten Start automatisch angelegt wird. Hat man einen LDAP Server im Einsatz, kann man diesen natürlich nehmen. Für dieses Beispiel bleiben wir aber bei einem lokalen Adminuser.

In der Application.properties Datei fügen wir die folgenden Zeilen hinzu :

camunda.bpm.admin-user.id=User
camunda.bpm.admin-user.email=mail@example.com
camunda.bpm.admin-user.first-name=Admin
camunda.bpm.admin-user.last-name=Mustermann
camunda.bpm.admin-user.password=password

Nun können wir uns nach dem Start der Anwendung mit dem Logininformationen
Benutzername:  User
Passwort: password

anmelden.

Zur Konfiguration eines ersten Filters für Tasks erweitern wir die Application.properties noch um die Zeile:

camunda.bpm.filter.create=All tasks

sodass die gesamte Application.properties Datei nun wie folgt aussieht:

# H2
spring.h2.console.enabled=true
spring.h2.console.path=/h2
# Datasource
spring.datasource.url=jdbc:h2:file:./target/db
spring.datasource.username=sa
spring.datasource.password=
spring.datasource.driver-class-name=org.h2.Driver
spring.jpa.database-platform=org.hibernate.dialect.H2Dialect

camunda.bpm.admin-user.id=User
camunda.bpm.admin-user.email=mail@example.com
camunda.bpm.admin-user.first-name=Admin
camunda.bpm.admin-user.last-name=Mustermann
camunda.bpm.admin-user.password=password
camunda.bpm.filter.create=All tasks

 

Schritt 4 : Definition des Prozesses

Über den Camunda Modeler definieren wir den folgenden Prozess:

process - In 8 Schritten um einen Workflow Prozess zu bauen
BPMN Beispiel Prozess
<?xml version="1.0" encoding="UTF-8"?>
<bpmn:definitions xmlns:bpmn="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:di="http://www.omg.org/spec/DD/20100524/DI" xmlns:dc="http://www.omg.org/spec/DD/20100524/DC" xmlns:camunda="http://camunda.org/schema/1.0/bpmn" id="Definitions_0xqpusa" targetNamespace="http://bpmn.io/schema/bpmn" exporter="Camunda Modeler" exporterVersion="2.0.1">
  <bpmn:process id="Testprocess" name="Testprocess" isExecutable="true" camunda:versionTag="1.0">
    <bpmn:startEvent id="StartEvent_1" camunda:asyncAfter="true" camunda:jobPriority="1">
      <bpmn:extensionElements>
        <camunda:failedJobRetryTimeCycle>30</camunda:failedJobRetryTimeCycle>
      </bpmn:extensionElements>
      <bpmn:outgoing>SequenceFlow_0wa4k97</bpmn:outgoing>
    </bpmn:startEvent>
    <bpmn:userTask id="Task_05lu7sy" name="Approve">
      <bpmn:incoming>SequenceFlow_1mv8z0d</bpmn:incoming>
      <bpmn:outgoing>SequenceFlow_096mb78</bpmn:outgoing>
    </bpmn:userTask>
    <bpmn:endEvent id="EndEvent_0wk49f5">
      <bpmn:incoming>SequenceFlow_096mb78</bpmn:incoming>
    </bpmn:endEvent>
    <bpmn:sequenceFlow id="SequenceFlow_0wa4k97" sourceRef="StartEvent_1" targetRef="Task_Sendping" />
    <bpmn:sequenceFlow id="SequenceFlow_096mb78" sourceRef="Task_05lu7sy" targetRef="EndEvent_0wk49f5" />
    <bpmn:serviceTask id="Task_Sendping" name="Send Ping" camunda:class="de.oderkerk.bpm.test.PingWebsiteDelegate">
      <bpmn:extensionElements>
        <camunda:inputOutput>
          <camunda:inputParameter name="stateOfPing" />
          <camunda:outputParameter name="stateOfPing" />
        </camunda:inputOutput>
      </bpmn:extensionElements>
      <bpmn:incoming>SequenceFlow_0wa4k97</bpmn:incoming>
      <bpmn:outgoing>SequenceFlow_1mv8z0d</bpmn:outgoing>
    </bpmn:serviceTask>
    <bpmn:sequenceFlow id="SequenceFlow_1mv8z0d" sourceRef="Task_Sendping" targetRef="Task_05lu7sy" />
  </bpmn:process>
  <bpmndi:BPMNDiagram id="BPMNDiagram_1">
    <bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="Testprocess">
      <bpmndi:BPMNShape id="_BPMNShape_StartEvent_2" bpmnElement="StartEvent_1">
        <dc:Bounds x="173" y="102" width="36" height="36" />
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape id="UserTask_0mxlnei_di" bpmnElement="Task_05lu7sy">
        <dc:Bounds x="545" y="80" width="100" height="80" />
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape id="EndEvent_0wk49f5_di" bpmnElement="EndEvent_0wk49f5">
        <dc:Bounds x="753" y="102" width="36" height="36" />
      </bpmndi:BPMNShape>
      <bpmndi:BPMNEdge id="SequenceFlow_0wa4k97_di" bpmnElement="SequenceFlow_0wa4k97">
        <di:waypoint x="209" y="120" />
        <di:waypoint x="267" y="120" />
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge id="SequenceFlow_096mb78_di" bpmnElement="SequenceFlow_096mb78">
        <di:waypoint x="645" y="120" />
        <di:waypoint x="753" y="120" />
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNShape id="ServiceTask_1v5muvw_di" bpmnElement="Task_Sendping">
        <dc:Bounds x="267" y="80" width="100" height="80" />
      </bpmndi:BPMNShape>
      <bpmndi:BPMNEdge id="SequenceFlow_1mv8z0d_di" bpmnElement="SequenceFlow_1mv8z0d">
        <di:waypoint x="367" y="120" />
        <di:waypoint x="545" y="120" />
      </bpmndi:BPMNEdge>
    </bpmndi:BPMNPlane>
  </bpmndi:BPMNDiagram>
</bpmn:definitions>

 

Der Service Task ruft eine Javaklasse mit einem JavaDelegate auf :

ServiceTask - In 8 Schritten um einen Workflow Prozess zu bauen
ServiceTask für Beispielprozess

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Den manuellen User Task legen wir wie folgt an :

Man Approve - In 8 Schritten um einen Workflow Prozess zu bauen
Manueller Approve Task

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Schritt 5 : Startklasse erstellen

Hier erstellen wir eine Klasse , die die Annontationen Spring Boot und EnableAutoconfiguration sowie die Main Methode beinhaltet :

package de.oderkerk.bpm.test;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
@EnableAutoConfiguration
public class TestApp {

  public static void main(String... args) {
    SpringApplication.run(TestApp.class, args);
  }

}

 

Schritt 6 : JavaDelegate erstellen

Wir haben in der Prozessdefinition dem Servicetask die Klasse PingWebsiteDelegate im Package de.oderkerk.bpm.test zugewiesen.

Diese Klasse implementiert das Interface org.camunda.bpm.engine.delegate.JavaDelegate und beinhaltet eine execute Methode.

public class PingWebsiteDelegate implements JavaDelegate {

  private final static Logger LOGGER = LoggerFactory.getLogger(PingWebsiteDelegate.class);

  public void execute(DelegateExecution execution) throws Exception {
    if (LOGGER.isDebugEnabled())
      LOGGER.debug("Starting execute Delegate PingWebsiteDelegate ");
    execution.setVariable("stateOfPingResult", getStatus("https://www.marcooderkerk.de"));

  }

Da wir hier nicht viel machen wollen, setzen wir in der Delegateexecution nur eine neue Variable stateOfPingResult mit dem Ergebnis der Verfügbarkeitsprüfung getStatus.

Die komplette Klasse sieht wie folgt aus:

package de.oderkerk.bpm.test;

import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.URL;

import org.camunda.bpm.engine.delegate.DelegateExecution;
import org.camunda.bpm.engine.delegate.JavaDelegate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class PingWebsiteDelegate implements JavaDelegate {

  private final static Logger LOGGER = LoggerFactory.getLogger(PingWebsiteDelegate.class);

  public void execute(DelegateExecution execution) throws Exception {
    if (LOGGER.isDebugEnabled())
      LOGGER.debug("Starting execute Delegate PingWebsiteDelegate ");
    execution.setVariable("stateOfPingResult", getStatus("https://www.marcooderkerk.de"));

  }

  public String getStatus(String url) throws IOException {

    String result = "";
    try {
      URL urlObj = new URL(url);
      HttpURLConnection con = (HttpURLConnection) urlObj.openConnection();
      con.setRequestMethod("GET");
      // Set connection timeout
      con.setConnectTimeout(3000);
      con.connect();

      int code = con.getResponseCode();
      if (code == 200) {
        result = "Online";
      }
    } catch (Exception e) {
      result = "Offline";
    }
    return result;
  }

}

 

Schritt 7 : Finalisierung

Zu guter letzt erzeugen wir im resource Verzeichnis einen Ordner META-INF und dort eine leere Datei processes.xml. Die Datei kann für unser Beispiel leer bleiben.

Schritt 8 : Build und Start der Anwendung

Wir führen einen Maven Build aus und starten anschließend die Anwendung

 

Ergebnis:

Nach dem Start sehen wir in der Konsole, dass eine Camunda instanz startet.

 ____                                 _         ____  ____  __  __
/ ___| __ _ _ __ ___  _   _ _ __   __| | __ _  | __ )|  _ \|  \/  |
| |   / _` | '_ ` _ \| | | | '_ \ / _` |/ _` | |  _ \| |_) | |\/| |
| |__| (_| | | | | | | |_| | | | | (_| | (_| | | |_) |  __/| |  | |
\____/\__,_|_| |_| |_|\__,_|_| |_|\__,_|\__,_| |____/|_|   |_|  |_|

  Spring-Boot:  (v2.0.5.RELEASE)
  Camunda BPM: (v7.9.0)
  Camunda BPM Spring Boot Starter: (v3.0.0)

Nachdem die Anwendung komplett hochgefahren ist, rufen wir in einem Webbrowser die Adresse http://localhost:8080 auf und melden und mit den administrierten Username und Passwort an. (User:password)

Wir landen auf der Camunda Tasklist und sehen auf der Linken Seite den erstellten Filter “All tasks”.

tasklist - In 8 Schritten um einen Workflow Prozess zu bauen

 

 

 

 

 

 

 

Über einen Klick auf Start Process können wir unseren Prozess manuell starten:

 

startProcess - In 8 Schritten um einen Workflow Prozess zu bauen
Schritt 1

 

 

 

 

 

 

 

Hier wählen wir den Testprocess aus

startProcess2 - In 8 Schritten um einen Workflow Prozess zu bauen
Schritt 2

 

 

 

 

 

 

 

Da wir keine weiteren Daten eingeben müssen , klicken wir einfach auf Start

Nun wechseln wir ins Cockpit und sehen , dass der Prozess nun am manuellen Schritt wartet :

cockpit - In 8 Schritten um einen Workflow Prozess zu bauen
Aktueller Status

 

 

 

 

 

 

 

Zurück in der Task List sehen wir nun den auf uns wartenden Vorgang und nach der Auswahl des Tasks Approve und klick auf Load Variables sehen wir das Ergebnis in der Variablen stateOfPingResult:

Ergebnis - In 8 Schritten um einen Workflow Prozess zu bauen

 

 

 

 

 

 

 

 

 

Das bedeutet unser Prozess hat den Ping ausgeführt. Der Form halber klicken wir auf Complete und der Prozess ist abgeschlossen. Zugegeben , dieses war ein sehr simpler Prozess, dieser ist aber leicht ausbaubar.

 

download button 1674764 640 - In 8 Schritten um einen Workflow Prozess zu bauen
Download Sourcen bei GitHub