Skip to content

Commit 03073a2

Browse files
#267 Implementierung einer Workflow-Engine (#285)
1 parent 645c73f commit 03073a2

File tree

10 files changed

+420
-4
lines changed

10 files changed

+420
-4
lines changed

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
.project
1313
.settings/
1414
.idea/
15+
.vscode/
1516

1617
# Mobile Tools for Java (J2ME)
1718
.mtj.tmp/

README.md

+35
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,41 @@ de.remsfal.auth.oidc.client-secret=<YOUR-SECRET>
6464
de.remsfal.auth.session.secret=<YOUR-CUSTOM-SESSION-SECRET>
6565
```
6666

67+
#### Zeebe run
68+
69+
Zeebe is a workflow engine for orchestrating microservices using BPMN 2.0 processes. This project integrates Zeebe with Camunda 8 BPMN Diagrams.
70+
71+
You can model your BPMN workflows in the Camunda Modeler. Please ensure that you are using the Camunda 8 BPMN Diagrams.
72+
73+
Make sure the IDs and job types of the tasks are defined, as they will be used by the JobWorkers later. You can find the task IDs in the XML or set them in the Modeler. The task types must match the task types in the BPMN file. You can set them in the Modeler under Task definition → Task type.
74+
75+
The Zeebe client automatically deploys the BPMN files to the Zeebe broker. It is configured in the application.properties file. Place your BPMN workflow files in the remsfal-service/src/main/resources/processes directory, and they will be automatically deployed to Zeebe.
76+
77+
You can start the process using the controller (see the example in ZeebeController.java). The controller will start the process with the given variables.
78+
79+
The Controller for starting the process is configured in ZeebeController.java. The controller will start the given process with the given variables. They will be passed to the process as JSON. For example, the ticket-process.bpmn file has the processID "ticket-process" and a variable "approve". The JSON should look like this:
80+
81+
{
82+
"processId": "ticket-process",
83+
"variables": {
84+
"approve": true
85+
}
86+
}
87+
88+
The JobWorkers will automatically pick up the tasks and execute them.
89+
The JobWorkers are configured in ZeebeWorker.java. The worker will pick up the tasks with the given task type and execute them. They are annotated with @ZeebeWorker. The task type must match the task type in the BPMN file. When the worker picks up the task, it will execute the given function. After the function is executed, the worker will complete the task and pass the result back to the Zeebe broker. Then the next task will be picked up by the worker.
90+
91+
This setup provides a foundational integration of Zeebe into your project.
92+
93+
To adapt this for your own workflow:
94+
95+
Create a new BPMN file to reflect your process.
96+
Update your JobWorkers to handle the tasks specific to your workflow.
97+
98+
99+
100+
#### Run
101+
67102
## JWT Token
68103

69104
For the JWT token its highly recommended to replace the default private key and public key with your own.

docker-compose.yml

+10-3
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
21
version: '3.8'
32
services:
43
mysql:
@@ -9,8 +8,6 @@ services:
98
- MYSQL_ALLOW_EMPTY_PASSWORD=yes
109
- MYSQL_DATABASE=REMSFAL
1110

12-
13-
1411
grafana:
1512
networks:
1613
- remsfal
@@ -29,6 +26,15 @@ services:
2926
ports:
3027
- '9090:9090'
3128

29+
zeebe:
30+
image: camunda/zeebe:latest
31+
ports:
32+
- "26500:26500"
33+
environment:
34+
- ZEEBE_BROKER_CLUSTER_SIZE=1
35+
- ZEEBE_BROKER_PARTITIONS_COUNT=1
36+
- ZEEBE_BROKER_REPLICATION_FACTOR=1
37+
3238
minio:
3339
image: minio/minio
3440
container_name: minio
@@ -41,6 +47,7 @@ services:
4147
- "9001:9001"
4248
volumes:
4349
- ./data/minio:/data
50+
4451

4552
networks:
4653
remsfal:

remsfal-service/pom.xml

+11
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,11 @@
121121
<artifactId>byte-buddy</artifactId>
122122
<version>1.15.11</version>
123123
</dependency>
124+
<dependency>
125+
<groupId>io.quarkiverse.zeebe</groupId>
126+
<artifactId>quarkus-zeebe</artifactId>
127+
<version>1.6.0</version>
128+
</dependency>
124129

125130
<!-- TEST dependencies -->
126131
<dependency>
@@ -148,6 +153,12 @@
148153
<artifactId>quarkus-jacoco</artifactId>
149154
<scope>test</scope>
150155
</dependency>
156+
<dependency>
157+
<groupId>io.quarkiverse.zeebe</groupId>
158+
<artifactId>quarkus-zeebe-test</artifactId>
159+
<version>1.6.0</version>
160+
<scope>test</scope>
161+
</dependency>
151162
<dependency>
152163
<groupId>io.quarkus</groupId>
153164
<artifactId>quarkus-smallrye-jwt</artifactId>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
package de.remsfal.service.control;
2+
3+
import io.camunda.zeebe.client.ZeebeClient;
4+
import jakarta.inject.Inject;
5+
import jakarta.ws.rs.POST;
6+
import jakarta.ws.rs.Path;
7+
import jakarta.ws.rs.core.Response;
8+
9+
import java.util.HashMap;
10+
import java.util.Map;
11+
import org.jboss.logging.Logger;
12+
13+
@Path("/zeebe")
14+
public class ZeebeController {
15+
16+
@Inject
17+
ZeebeClient zeebeClient;
18+
19+
@Inject
20+
Logger logger;
21+
22+
@POST
23+
@Path("/start")
24+
public Response startWorkflow(Map<String, Object> requestBody) {
25+
try {
26+
if (!requestBody.containsKey("processId")) {
27+
throw new IllegalArgumentException("Missing 'processId' in request body.");
28+
}
29+
30+
String processId = (String) requestBody.get("processId");
31+
Map<String, Object> variables = new HashMap<>();
32+
33+
Object rawVars = requestBody.get("variables");
34+
if (rawVars instanceof Map<?, ?> rawMap) {
35+
for (Map.Entry<?, ?> entry : rawMap.entrySet()) {
36+
if (entry.getKey() instanceof String key) {
37+
variables.put(key, entry.getValue());
38+
}
39+
}
40+
}
41+
42+
var workflowInstance = zeebeClient
43+
.newCreateInstanceCommand()
44+
.bpmnProcessId(processId)
45+
.latestVersion()
46+
.variables(variables)
47+
.send()
48+
.join();
49+
50+
return Response.ok(
51+
String.format("Workflow '%s' started with key: %d",
52+
processId,
53+
workflowInstance.getProcessInstanceKey()))
54+
.build();
55+
56+
} catch (IllegalArgumentException e) {
57+
logger.error("Bad request: " + e.getMessage(), e);
58+
return Response.status(Response.Status.BAD_REQUEST)
59+
.entity(String.format("Error: %s", e.getMessage()))
60+
.build();
61+
}
62+
}
63+
64+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
package de.remsfal.service.zeebe;
2+
3+
import io.camunda.zeebe.client.api.response.ActivatedJob;
4+
import io.camunda.zeebe.client.api.worker.JobClient;
5+
import io.quarkiverse.zeebe.JobWorker;
6+
import jakarta.inject.Inject;
7+
8+
import java.util.Map;
9+
10+
import org.jboss.logging.Logger;
11+
12+
public class ZeebeWorker {
13+
14+
@Inject
15+
Logger logger;
16+
17+
@JobWorker(type = "approve-ticket")
18+
public void completeTicket(final JobClient client, final ActivatedJob job) {
19+
logger.info("Task Handler approve-ticket was called.");
20+
client.newCompleteCommand(job.getKey())
21+
.variables(Map.of("approveTicketWasCalled", true))
22+
.send().join();
23+
}
24+
25+
@JobWorker(type = "reject-ticket")
26+
public void rejectTicket(final JobClient client, final ActivatedJob job) {
27+
logger.info("Task Handler reject-ticket was called.");
28+
client.newCompleteCommand(job.getKey())
29+
.variables(Map.of("rejectTicketWasCalled", true))
30+
.send().join();
31+
}
32+
33+
@JobWorker(type = "close-ticket")
34+
public void closeTicket(final JobClient client, final ActivatedJob job) {
35+
logger.info("Task Handler reject-ticket was called.");
36+
client.newCompleteCommand(job.getKey())
37+
.variables(Map.of("ticketClosed", true))
38+
.send().join();
39+
}
40+
41+
}

remsfal-service/src/main/resources/application.properties

+12-1
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,17 @@ de.remsfal.auth.oidc.client-id=821093255871-8pnrdogfojdc36mqooovf951ao9r4o8i.app
4141
de.remsfal.auth.oidc.client-secret=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
4242
de.remsfal.auth.session.secret=removeMe
4343

44+
# quarkus.zeebe configuration
45+
quarkus.zeebe.resources.enabled=true
46+
quarkus.zeebe.resources.location=processes
47+
quarkus.zeebe.client.request-timeout=30s
48+
49+
# Dev Profile Configuration
50+
%dev.quarkus.zeebe.devservices.enabled=true
51+
%dev.quarkus.zeebe.dev-mode.watch-bpmn-dir=true
52+
%dev.quarkus.zeebe.dev-mode.watch-bpmn-files=true
53+
%dev.quarkus.zeebe.dev-mode.watch-job-worker=true
54+
4455
# MinIO configuration for Dev Profile
4556
%dev.quarkus.minio.devservices.enabled=false
4657
%dev.quarkus.minio.url=http://localhost:9000
@@ -50,4 +61,4 @@ de.remsfal.auth.session.secret=removeMe
5061

5162
de.remsfal.auth.jwt.private-key-location=privateKey.pem
5263
de.remsfal.auth.jwt.public-key-location=publicKey.pem
53-
de.remsfal.auth.jwt.issuer=REMSFAL
64+
de.remsfal.auth.jwt.issuer=REMSFAL
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<bpmn:definitions xmlns:bpmn="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:dc="http://www.omg.org/spec/DD/20100524/DC" xmlns:zeebe="http://camunda.org/schema/zeebe/1.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:di="http://www.omg.org/spec/DD/20100524/DI" xmlns:modeler="http://camunda.org/schema/modeler/1.0" id="Definitions_0378vlr" targetNamespace="http://bpmn.io/schema/bpmn" exporter="Camunda Modeler" exporterVersion="5.30.0" modeler:executionPlatform="Camunda Cloud" modeler:executionPlatformVersion="8.6.0">
3+
<bpmn:process id="ticket-process" isExecutable="true">
4+
<bpmn:startEvent id="StartEvent_1" name="Review Ticket">
5+
<bpmn:outgoing>Flow_15eitzn</bpmn:outgoing>
6+
</bpmn:startEvent>
7+
<bpmn:exclusiveGateway id="approve" name="Ticket approved?">
8+
<bpmn:incoming>Flow_15eitzn</bpmn:incoming>
9+
<bpmn:outgoing>Flow_0qa5dor</bpmn:outgoing>
10+
<bpmn:outgoing>Flow_1mwuyrx</bpmn:outgoing>
11+
</bpmn:exclusiveGateway>
12+
<bpmn:sequenceFlow id="Flow_15eitzn" sourceRef="StartEvent_1" targetRef="approve" />
13+
<bpmn:sequenceFlow id="Flow_0qa5dor" name="true" sourceRef="approve" targetRef="approve-ticket">
14+
<bpmn:conditionExpression xsi:type="bpmn:tFormalExpression">=approve = true</bpmn:conditionExpression>
15+
</bpmn:sequenceFlow>
16+
<bpmn:sequenceFlow id="Flow_1mwuyrx" name="false" sourceRef="approve" targetRef="reject-ticket">
17+
<bpmn:conditionExpression xsi:type="bpmn:tFormalExpression">=approve = false</bpmn:conditionExpression>
18+
</bpmn:sequenceFlow>
19+
<bpmn:sequenceFlow id="Flow_0zw3wml" sourceRef="approve-ticket" targetRef="Gateway_1konots" />
20+
<bpmn:endEvent id="Event_06ekl28" name="Ticket closed">
21+
<bpmn:incoming>Flow_0zq29tr</bpmn:incoming>
22+
</bpmn:endEvent>
23+
<bpmn:sequenceFlow id="Flow_0h45x13" sourceRef="reject-ticket" targetRef="Gateway_1konots" />
24+
<bpmn:serviceTask id="reject-ticket" name="Reject-Ticket">
25+
<bpmn:extensionElements>
26+
<zeebe:taskDefinition type="reject-ticket" retries="3" />
27+
</bpmn:extensionElements>
28+
<bpmn:incoming>Flow_1mwuyrx</bpmn:incoming>
29+
<bpmn:outgoing>Flow_0h45x13</bpmn:outgoing>
30+
</bpmn:serviceTask>
31+
<bpmn:serviceTask id="approve-ticket" name="Approve Ticket">
32+
<bpmn:extensionElements>
33+
<zeebe:taskDefinition type="approve-ticket" retries="3" />
34+
</bpmn:extensionElements>
35+
<bpmn:incoming>Flow_0qa5dor</bpmn:incoming>
36+
<bpmn:outgoing>Flow_0zw3wml</bpmn:outgoing>
37+
</bpmn:serviceTask>
38+
<bpmn:sequenceFlow id="Flow_0zq29tr" sourceRef="close-ticket" targetRef="Event_06ekl28" />
39+
<bpmn:serviceTask id="close-ticket" name="Close Ticket">
40+
<bpmn:extensionElements>
41+
<zeebe:taskDefinition type="close-ticket" retries="3" />
42+
</bpmn:extensionElements>
43+
<bpmn:incoming>Flow_1j6b6g3</bpmn:incoming>
44+
<bpmn:outgoing>Flow_0zq29tr</bpmn:outgoing>
45+
</bpmn:serviceTask>
46+
<bpmn:exclusiveGateway id="Gateway_1konots">
47+
<bpmn:incoming>Flow_0zw3wml</bpmn:incoming>
48+
<bpmn:incoming>Flow_0h45x13</bpmn:incoming>
49+
<bpmn:outgoing>Flow_1j6b6g3</bpmn:outgoing>
50+
</bpmn:exclusiveGateway>
51+
<bpmn:sequenceFlow id="Flow_1j6b6g3" sourceRef="Gateway_1konots" targetRef="close-ticket" />
52+
</bpmn:process>
53+
<bpmndi:BPMNDiagram id="BPMNDiagram_1">
54+
<bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="ticket-process">
55+
<bpmndi:BPMNShape id="Gateway_1d0klon_di" bpmnElement="approve" isMarkerVisible="true">
56+
<dc:Bounds x="275" y="155" width="50" height="50" />
57+
<bpmndi:BPMNLabel>
58+
<dc:Bounds x="338" y="170" width="84" height="14" />
59+
</bpmndi:BPMNLabel>
60+
</bpmndi:BPMNShape>
61+
<bpmndi:BPMNShape id="Activity_0r8f34r_di" bpmnElement="reject-ticket">
62+
<dc:Bounds x="390" y="240" width="100" height="80" />
63+
<bpmndi:BPMNLabel />
64+
</bpmndi:BPMNShape>
65+
<bpmndi:BPMNShape id="Activity_0dkl2vz_di" bpmnElement="approve-ticket">
66+
<dc:Bounds x="390" y="50" width="100" height="80" />
67+
<bpmndi:BPMNLabel />
68+
</bpmndi:BPMNShape>
69+
<bpmndi:BPMNShape id="Event_06ekl28_di" bpmnElement="Event_06ekl28">
70+
<dc:Bounds x="792" y="162" width="36" height="36" />
71+
<bpmndi:BPMNLabel>
72+
<dc:Bounds x="778" y="205" width="64" height="14" />
73+
</bpmndi:BPMNLabel>
74+
</bpmndi:BPMNShape>
75+
<bpmndi:BPMNShape id="Activity_00nqiiy_di" bpmnElement="close-ticket">
76+
<dc:Bounds x="630" y="140" width="100" height="80" />
77+
</bpmndi:BPMNShape>
78+
<bpmndi:BPMNShape id="Gateway_1konots_di" bpmnElement="Gateway_1konots" isMarkerVisible="true">
79+
<dc:Bounds x="535" y="155" width="50" height="50" />
80+
</bpmndi:BPMNShape>
81+
<bpmndi:BPMNShape id="StartEvent_1_di" bpmnElement="StartEvent_1">
82+
<dc:Bounds x="172" y="162" width="36" height="36" />
83+
<bpmndi:BPMNLabel>
84+
<dc:Bounds x="156" y="205" width="68" height="14" />
85+
</bpmndi:BPMNLabel>
86+
</bpmndi:BPMNShape>
87+
<bpmndi:BPMNEdge id="Flow_15eitzn_di" bpmnElement="Flow_15eitzn">
88+
<di:waypoint x="208" y="180" />
89+
<di:waypoint x="275" y="180" />
90+
</bpmndi:BPMNEdge>
91+
<bpmndi:BPMNEdge id="Flow_0qa5dor_di" bpmnElement="Flow_0qa5dor">
92+
<di:waypoint x="300" y="155" />
93+
<di:waypoint x="300" y="90" />
94+
<di:waypoint x="390" y="90" />
95+
<bpmndi:BPMNLabel>
96+
<dc:Bounds x="331" y="73" width="19" height="14" />
97+
</bpmndi:BPMNLabel>
98+
</bpmndi:BPMNEdge>
99+
<bpmndi:BPMNEdge id="Flow_1mwuyrx_di" bpmnElement="Flow_1mwuyrx">
100+
<di:waypoint x="300" y="205" />
101+
<di:waypoint x="300" y="280" />
102+
<di:waypoint x="390" y="280" />
103+
<bpmndi:BPMNLabel>
104+
<dc:Bounds x="329" y="255" width="24" height="14" />
105+
</bpmndi:BPMNLabel>
106+
</bpmndi:BPMNEdge>
107+
<bpmndi:BPMNEdge id="Flow_0zw3wml_di" bpmnElement="Flow_0zw3wml">
108+
<di:waypoint x="490" y="90" />
109+
<di:waypoint x="560" y="90" />
110+
<di:waypoint x="560" y="155" />
111+
</bpmndi:BPMNEdge>
112+
<bpmndi:BPMNEdge id="Flow_0h45x13_di" bpmnElement="Flow_0h45x13">
113+
<di:waypoint x="490" y="280" />
114+
<di:waypoint x="560" y="280" />
115+
<di:waypoint x="560" y="205" />
116+
</bpmndi:BPMNEdge>
117+
<bpmndi:BPMNEdge id="Flow_0zq29tr_di" bpmnElement="Flow_0zq29tr">
118+
<di:waypoint x="730" y="180" />
119+
<di:waypoint x="792" y="180" />
120+
</bpmndi:BPMNEdge>
121+
<bpmndi:BPMNEdge id="Flow_1j6b6g3_di" bpmnElement="Flow_1j6b6g3">
122+
<di:waypoint x="585" y="180" />
123+
<di:waypoint x="630" y="180" />
124+
</bpmndi:BPMNEdge>
125+
</bpmndi:BPMNPlane>
126+
</bpmndi:BPMNDiagram>
127+
</bpmn:definitions>

0 commit comments

Comments
 (0)