Compare commits
9 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
248b1c5d2f | ||
|
|
28c36a589d | ||
|
|
817c683ad5 | ||
|
|
2186248b8b | ||
|
|
5b5f81b3f8 | ||
|
|
48ee99e182 | ||
|
|
fda0bb30bf | ||
|
|
ec62e6e3c9 | ||
|
|
00bfe0fb81 |
9
.github/workflows/build-and-publish.yaml
vendored
9
.github/workflows/build-and-publish.yaml
vendored
@@ -4,6 +4,7 @@ on:
|
||||
push:
|
||||
branches:
|
||||
- 'main'
|
||||
- 'camunda-8'
|
||||
|
||||
jobs:
|
||||
build-test-utils:
|
||||
@@ -76,9 +77,9 @@ jobs:
|
||||
- name: Login to Docker Hub
|
||||
run: docker login -u ${{ secrets.DOCKER_USER }} -p '${{ secrets.DOCKER_TOKEN }}'
|
||||
- name: Build Docker image
|
||||
run: docker build -t ${{env.repo}}-loan-agreement .
|
||||
run: docker build -t ${{env.repo}}-loan-agreement-camunda-8 .
|
||||
- name: Publish Docker image
|
||||
run: docker push ${{env.repo}}-loan-agreement
|
||||
run: docker push ${{env.repo}}-loan-agreement-camunda-8
|
||||
|
||||
recommendation-build:
|
||||
name: Build Recommendation JAR
|
||||
@@ -131,6 +132,6 @@ jobs:
|
||||
- name: Login to Docker Hub
|
||||
run: docker login -u ${{ secrets.DOCKER_USER }} -p '${{ secrets.DOCKER_TOKEN }}'
|
||||
- name: Build Docker image
|
||||
run: docker build -t ${{env.repo}}-recommendation .
|
||||
run: docker build -t ${{env.repo}}-recommendation-camunda-8 .
|
||||
- name: Publish Docker image
|
||||
run: docker push ${{env.repo}}-recommendation
|
||||
run: docker push ${{env.repo}}-recommendation-camunda-8
|
||||
15
README.md
15
README.md
@@ -2,10 +2,25 @@
|
||||
|
||||
An example to show how you could use clean architecture and DDD and their advantages with Camunda.
|
||||
|
||||
I also wrote a blog post to show how clean architecture could help you to update to Camunda Platform 8 without
|
||||
touching your domain centered code: [How Clean Architecture helps you migrating Camunda Platform 7 to 8](https://www.novatec-gmbh.de/en/blog/how-clean-architecture-helps-you-migrating-camunda-platform-7-to-8/).
|
||||
|
||||
## 🚀Features
|
||||
|
||||
The [BPMN process](assets/processes/loan_agreement.png) which start a [second process](assets/processes/cross_selling_recommendation.png) via message correlation should represent a tiny business process just to demonstrate the architecture.
|
||||
|
||||
Configure your [Camunda Platform 8 SaaS](https://camunda.com/get-started/) to connect to using the [following properties](https://github.com/camunda-community-hub/spring-zeebe#configuring-camunda-platform-8-saas-connection) .
|
||||
|
||||
If you want to run [Zeebe locally](https://docs.camunda.io/docs/self-managed/platform-deployment/kubernetes-helm/) you need to use the following properties ([source](https://github.com/camunda-community-hub/spring-zeebe#configuring-camunda-platform-8-saas-connection)):
|
||||
|
||||
```yaml
|
||||
zeebe:
|
||||
client:
|
||||
broker:
|
||||
gateway-address: localhost:26500
|
||||
security:
|
||||
plaintext: true
|
||||
```
|
||||
### 🛫Start the process
|
||||
|
||||
With the following POST request, you could start the process:
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
|
||||
<version.springboot>2.6.4</version.springboot>
|
||||
<version.camunda>7.17.0</version.camunda>
|
||||
<version.zeebe>8.0.1</version.zeebe>
|
||||
<version.junit5>5.8.2</version.junit5>
|
||||
<version.lombok>1.18.24</version.lombok>
|
||||
<version.domainprimitives>0.1.0</version.domainprimitives>
|
||||
@@ -37,9 +37,9 @@
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.camunda.bpm</groupId>
|
||||
<artifactId>camunda-bom</artifactId>
|
||||
<version>${version.camunda}</version>
|
||||
<groupId>io.camunda</groupId>
|
||||
<artifactId>zeebe-bom</artifactId>
|
||||
<version>${version.zeebe}</version>
|
||||
<scope>import</scope>
|
||||
<type>pom</type>
|
||||
</dependency>
|
||||
@@ -48,23 +48,35 @@
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.camunda.bpm.springboot</groupId>
|
||||
<artifactId>camunda-bpm-spring-boot-starter-rest</artifactId>
|
||||
<groupId>io.camunda</groupId>
|
||||
<artifactId>spring-zeebe</artifactId>
|
||||
<version>${version.zeebe}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.camunda.bpm.springboot</groupId>
|
||||
<artifactId>camunda-bpm-spring-boot-starter-webapp</artifactId>
|
||||
<groupId>io.camunda</groupId>
|
||||
<artifactId>spring-zeebe-starter</artifactId>
|
||||
<version>${version.zeebe}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-webflux</artifactId>
|
||||
<groupId>io.camunda</groupId>
|
||||
<artifactId>spring-zeebe-util</artifactId>
|
||||
<version>${version.zeebe}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.camunda.bpm.springboot</groupId>
|
||||
<artifactId>camunda-bpm-spring-boot-starter-test</artifactId>
|
||||
<groupId>io.camunda</groupId>
|
||||
<artifactId>spring-zeebe-test</artifactId>
|
||||
<version>${version.zeebe}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.camunda</groupId>
|
||||
<artifactId>zeebe-process-test-extension</artifactId>
|
||||
<version>${version.zeebe}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
@@ -72,6 +84,11 @@
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-webflux</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.h2database</groupId>
|
||||
<artifactId>h2</artifactId>
|
||||
@@ -118,21 +135,6 @@
|
||||
<version>${version.junit5}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.camunda.bpm.extension</groupId>
|
||||
<artifactId>camunda-bpm-junit5</artifactId>
|
||||
<version>${version.bpmAssert}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.camunda.community.mockito</groupId>
|
||||
<artifactId>camunda-platform-7-mockito</artifactId>
|
||||
<version>${version.camundaMockito}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.squareup.okhttp3</groupId>
|
||||
<artifactId>okhttp</artifactId>
|
||||
@@ -164,6 +166,14 @@
|
||||
|
||||
<build>
|
||||
<finalName>${project.artifactId}</finalName>
|
||||
<testResources>
|
||||
<testResource>
|
||||
<directory>${project.basedir}/src/test/resources</directory>
|
||||
</testResource>
|
||||
<testResource>
|
||||
<directory>${project.basedir}/src/main/resources</directory>
|
||||
</testResource>
|
||||
</testResources>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
package de.weinbrecht.luc.bpm.architecture.loan.agreement;
|
||||
|
||||
import io.camunda.zeebe.spring.client.EnableZeebeClient;
|
||||
import io.camunda.zeebe.spring.client.annotation.ZeebeDeployment;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
@Configuration
|
||||
@EnableZeebeClient
|
||||
@ZeebeDeployment(resources = {"classpath:*.bpmn", "classpath:*.dmn"})
|
||||
public class ApplicationConfiguration {
|
||||
}
|
||||
@@ -6,6 +6,10 @@ public class ProcessConstants {
|
||||
public static final String LOAN_AGREEMENT_NUMBER = "loanAgreementNumber";
|
||||
public static final String RECOMMENDATION_START_EVENT_MESSAGE_REF = "crossSellingPotentialDiscoveredMessage";
|
||||
public static final String RECOMMENDATION_CUSTOMER_NUMBER = "customerNumber";
|
||||
public static final String BUSINESS_KEY = "businessKey";
|
||||
|
||||
public static final String LOAN_AGREEMENT_TASK = "approve-loan-agreement";
|
||||
public static final String LOAN_REJECTION_TASK = "reject-loan-agreement";
|
||||
public static final String SEND_CROSS_SELLING_RECOMMENDATION_TASK = "send-cross-selling-recommendation";
|
||||
|
||||
}
|
||||
|
||||
@@ -2,24 +2,23 @@ package de.weinbrecht.luc.bpm.architecture.loan.agreement.adapter.in.process;
|
||||
|
||||
import de.weinbrecht.luc.bpm.architecture.loan.agreement.domain.model.LoanAgreementNumber;
|
||||
import de.weinbrecht.luc.bpm.architecture.loan.agreement.usecase.in.LoanAgreementStatusCommand;
|
||||
import io.camunda.zeebe.spring.client.annotation.ZeebeVariable;
|
||||
import io.camunda.zeebe.spring.client.annotation.ZeebeWorker;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.camunda.bpm.engine.delegate.DelegateExecution;
|
||||
import org.camunda.bpm.engine.delegate.JavaDelegate;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import static de.weinbrecht.luc.bpm.architecture.loan.agreement.adapter.common.ProcessConstants.LOAN_AGREEMENT_NUMBER;
|
||||
import static de.weinbrecht.luc.bpm.architecture.loan.agreement.adapter.common.ProcessConstants.LOAN_AGREEMENT_TASK;
|
||||
|
||||
@Slf4j
|
||||
@RequiredArgsConstructor
|
||||
@Component
|
||||
public class ApproveLoanAgreement implements JavaDelegate {
|
||||
public class ApproveLoanAgreement {
|
||||
|
||||
private final LoanAgreementStatusCommand loanAgreementStatusCommand;
|
||||
|
||||
@Override
|
||||
public void execute(DelegateExecution delegateExecution) {
|
||||
Long loanAgreementNumber = (Long) delegateExecution.getVariable(LOAN_AGREEMENT_NUMBER);
|
||||
loanAgreementStatusCommand.accept(new LoanAgreementNumber(loanAgreementNumber));
|
||||
@ZeebeWorker(type = LOAN_AGREEMENT_TASK, autoComplete = true)
|
||||
public void handleJobFoo(@ZeebeVariable Number loanAgreementNumber) {
|
||||
loanAgreementStatusCommand.accept(new LoanAgreementNumber(loanAgreementNumber.longValue()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,24 +2,23 @@ package de.weinbrecht.luc.bpm.architecture.loan.agreement.adapter.in.process;
|
||||
|
||||
import de.weinbrecht.luc.bpm.architecture.loan.agreement.domain.model.LoanAgreementNumber;
|
||||
import de.weinbrecht.luc.bpm.architecture.loan.agreement.usecase.in.LoanAgreementStatusCommand;
|
||||
import io.camunda.zeebe.spring.client.annotation.ZeebeVariable;
|
||||
import io.camunda.zeebe.spring.client.annotation.ZeebeWorker;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.camunda.bpm.engine.delegate.DelegateExecution;
|
||||
import org.camunda.bpm.engine.delegate.JavaDelegate;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import static de.weinbrecht.luc.bpm.architecture.loan.agreement.adapter.common.ProcessConstants.LOAN_AGREEMENT_NUMBER;
|
||||
import static de.weinbrecht.luc.bpm.architecture.loan.agreement.adapter.common.ProcessConstants.LOAN_REJECTION_TASK;
|
||||
|
||||
@Slf4j
|
||||
@RequiredArgsConstructor
|
||||
@Component
|
||||
public class RejectionLoanAgreement implements JavaDelegate {
|
||||
public class RejectionLoanAgreement {
|
||||
|
||||
private final LoanAgreementStatusCommand loanAgreementStatusCommand;
|
||||
|
||||
@Override
|
||||
public void execute(DelegateExecution delegateExecution) {
|
||||
Long loanAgreementNumber = (Long) delegateExecution.getVariable(LOAN_AGREEMENT_NUMBER);
|
||||
loanAgreementStatusCommand.reject(new LoanAgreementNumber(loanAgreementNumber));
|
||||
@ZeebeWorker(type = LOAN_REJECTION_TASK, autoComplete = true)
|
||||
public void handleJobFoo(@ZeebeVariable Number loanAgreementNumber) {
|
||||
loanAgreementStatusCommand.reject(new LoanAgreementNumber(loanAgreementNumber.longValue()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,25 +5,26 @@ import de.weinbrecht.luc.bpm.architecture.loan.agreement.domain.model.LoanAgreem
|
||||
import de.weinbrecht.luc.bpm.architecture.loan.agreement.domain.model.LoanAgreementNumber;
|
||||
import de.weinbrecht.luc.bpm.architecture.loan.agreement.usecase.out.LoanAgreementQuery;
|
||||
import de.weinbrecht.luc.bpm.architecture.loan.agreement.usecase.out.RecommendationTrigger;
|
||||
import io.camunda.zeebe.spring.client.annotation.ZeebeVariable;
|
||||
import io.camunda.zeebe.spring.client.annotation.ZeebeWorker;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.camunda.bpm.engine.delegate.DelegateExecution;
|
||||
import org.camunda.bpm.engine.delegate.JavaDelegate;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import static de.weinbrecht.luc.bpm.architecture.loan.agreement.adapter.common.ProcessConstants.LOAN_AGREEMENT_NUMBER;
|
||||
import static de.weinbrecht.luc.bpm.architecture.loan.agreement.adapter.common.ProcessConstants.SEND_CROSS_SELLING_RECOMMENDATION_TASK;
|
||||
|
||||
@RequiredArgsConstructor
|
||||
@Component
|
||||
public class SendCrossSellingRecommendation implements JavaDelegate {
|
||||
public class SendCrossSellingRecommendation {
|
||||
|
||||
private final RecommendationTrigger recommendationTrigger;
|
||||
private final LoanAgreementQuery loanAgreementQuery;
|
||||
|
||||
@Override
|
||||
public void execute(DelegateExecution execution) {
|
||||
Long loanNumber = (Long) execution.getVariable(LOAN_AGREEMENT_NUMBER);
|
||||
LoanAgreement loanAgreement = loanAgreementQuery.loadByNumber(new LoanAgreementNumber(loanNumber));
|
||||
@ZeebeWorker(type = SEND_CROSS_SELLING_RECOMMENDATION_TASK, autoComplete = true)
|
||||
public void handleJobFoo(@ZeebeVariable Number loanAgreementNumber, @ZeebeVariable String businessKey) {
|
||||
LoanAgreement loanAgreement = loanAgreementQuery.loadByNumber(
|
||||
new LoanAgreementNumber(loanAgreementNumber.longValue())
|
||||
);
|
||||
|
||||
recommendationTrigger.startLoanAgreement(new CaseId(execution.getBusinessKey()), loanAgreement);
|
||||
recommendationTrigger.startLoanAgreement(new CaseId(businessKey), loanAgreement);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
package de.weinbrecht.luc.bpm.architecture.loan.agreement.adapter.out.process;
|
||||
|
||||
public class CouldNotPublishMessageException extends RuntimeException {
|
||||
public CouldNotPublishMessageException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
}
|
||||
@@ -3,26 +3,33 @@ package de.weinbrecht.luc.bpm.architecture.loan.agreement.adapter.out.process;
|
||||
import de.weinbrecht.luc.bpm.architecture.loan.agreement.domain.model.CaseId;
|
||||
import de.weinbrecht.luc.bpm.architecture.loan.agreement.domain.model.LoanAgreementNumber;
|
||||
import de.weinbrecht.luc.bpm.architecture.loan.agreement.usecase.out.WorkflowCommand;
|
||||
import io.camunda.zeebe.client.ZeebeClient;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.camunda.bpm.engine.RuntimeService;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import static de.weinbrecht.luc.bpm.architecture.loan.agreement.adapter.common.ProcessConstants.LOAN_AGREEMENT_NUMBER;
|
||||
import static de.weinbrecht.luc.bpm.architecture.loan.agreement.adapter.common.ProcessConstants.LOAN_START_EVENT_MESSAGE_REF;
|
||||
import static de.weinbrecht.luc.bpm.architecture.loan.agreement.adapter.common.ProcessConstants.*;
|
||||
|
||||
@RequiredArgsConstructor
|
||||
@Component
|
||||
class ProcessEngineClient implements WorkflowCommand {
|
||||
|
||||
private final RuntimeService runtimeService;
|
||||
private final ZeebeClient client;
|
||||
|
||||
@Override
|
||||
public void startLoanAgreement(CaseId caseId, LoanAgreementNumber loanAgreementNumber) {
|
||||
Map<String, Object> processVariables = new HashMap<>();
|
||||
processVariables.put(LOAN_AGREEMENT_NUMBER, loanAgreementNumber.getValue());
|
||||
runtimeService.startProcessInstanceByMessage(LOAN_START_EVENT_MESSAGE_REF, caseId.getValue(), processVariables);
|
||||
processVariables.put(BUSINESS_KEY, caseId.getValue());
|
||||
client.newPublishMessageCommand()
|
||||
.messageName(LOAN_START_EVENT_MESSAGE_REF)
|
||||
.correlationKey("")
|
||||
.variables(processVariables)
|
||||
.send()
|
||||
.exceptionally(throwable -> {
|
||||
throw new CouldNotPublishMessageException(throwable);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,9 +5,9 @@ spring:
|
||||
hibernate:
|
||||
ddl-auto: create-drop
|
||||
|
||||
camunda.bpm.admin-user:
|
||||
id: admin
|
||||
password: pw
|
||||
generic-properties:
|
||||
properties:
|
||||
initializeTelemetry: false
|
||||
zeebe:
|
||||
client:
|
||||
cloud:
|
||||
cluster-id: <your cluster ID>
|
||||
client-id: <your client ID>
|
||||
client-secret: <you secret>
|
||||
|
||||
@@ -1,26 +1,26 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<definitions xmlns="https://www.omg.org/spec/DMN/20191111/MODEL/" xmlns:dmndi="https://www.omg.org/spec/DMN/20191111/DMNDI/" xmlns:dc="http://www.omg.org/spec/DMN/20180521/DC/" xmlns:modeler="http://camunda.org/schema/modeler/1.0" xmlns:biodi="http://bpmn.io/schema/dmn/biodi/2.0" xmlns:camunda="http://camunda.org/schema/1.0/dmn" id="Definitions_11dxtis" name="DRD" namespace="http://camunda.org/schema/1.0/dmn" exporter="Camunda Modeler" exporterVersion="5.0.0" modeler:executionPlatform="Camunda Platform" modeler:executionPlatformVersion="7.17.0">
|
||||
<definitions xmlns="https://www.omg.org/spec/DMN/20191111/MODEL/" xmlns:dmndi="https://www.omg.org/spec/DMN/20191111/DMNDI/" xmlns:dc="http://www.omg.org/spec/DMN/20180521/DC/" xmlns:modeler="http://camunda.org/schema/modeler/1.0" id="Definitions_0ig9g89" name="DRD" namespace="http://camunda.org/schema/1.0/dmn" exporter="Camunda Modeler" exporterVersion="5.0.0" modeler:executionPlatform="Camunda Cloud" modeler:executionPlatformVersion="8.0.0">
|
||||
<decision id="approvement-check" name="Approve Loan Agreement">
|
||||
<decisionTable id="DecisionTable_0ffqkch">
|
||||
<input id="Input_1" biodi:width="192" camunda:inputVariable="loanAgreementNumber">
|
||||
<inputExpression id="InputExpression_1" typeRef="integer">
|
||||
<decisionTable id="DecisionTable_0ojcgph">
|
||||
<input id="Input_1">
|
||||
<inputExpression id="InputExpression_1" typeRef="number">
|
||||
<text>loanAgreementNumber</text>
|
||||
</inputExpression>
|
||||
</input>
|
||||
<output id="Output_1" name="approved" typeRef="boolean" biodi:width="192" />
|
||||
<rule id="DecisionRule_0p9ijdl">
|
||||
<inputEntry id="UnaryTests_1qou5p9">
|
||||
<output id="Output_1" name="approved" typeRef="boolean" />
|
||||
<rule id="DecisionRule_1ofiexe">
|
||||
<inputEntry id="UnaryTests_0xwmkci">
|
||||
<text>>= 5</text>
|
||||
</inputEntry>
|
||||
<outputEntry id="LiteralExpression_1hlf608">
|
||||
<outputEntry id="LiteralExpression_0i1nxma">
|
||||
<text>false</text>
|
||||
</outputEntry>
|
||||
</rule>
|
||||
<rule id="DecisionRule_1arucld">
|
||||
<inputEntry id="UnaryTests_1vqubek">
|
||||
<rule id="DecisionRule_0t4w3lo">
|
||||
<inputEntry id="UnaryTests_1vli5s3">
|
||||
<text>< 5</text>
|
||||
</inputEntry>
|
||||
<outputEntry id="LiteralExpression_1ovr1wt">
|
||||
<outputEntry id="LiteralExpression_1puh4m4">
|
||||
<text>true</text>
|
||||
</outputEntry>
|
||||
</rule>
|
||||
|
||||
@@ -1,157 +1,166 @@
|
||||
<?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:dc="http://www.omg.org/spec/DD/20100524/DC" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:camunda="http://camunda.org/schema/1.0/bpmn" xmlns:di="http://www.omg.org/spec/DD/20100524/DI" xmlns:modeler="http://camunda.org/schema/modeler/1.0" id="Definitions_1yngi5u" targetNamespace="http://bpmn.io/schema/bpmn" exporter="Camunda Modeler" exporterVersion="5.0.0" modeler:executionPlatform="Camunda Platform" modeler:executionPlatformVersion="7.17.0">
|
||||
<bpmn:collaboration id="Collaboration_0w4ikjr">
|
||||
<bpmn:participant id="Participant_1p6ilgg" name="Loan Agreement example" processRef="Loan_Agreement" />
|
||||
<bpmn:participant id="Participant_0y932pu" name="Third-party-legacy System" />
|
||||
<bpmn:participant id="Participant_0z5fkq5" name="Third-party-legacy System" />
|
||||
<bpmn:messageFlow id="Flow_1ytdopc" sourceRef="RejectLoanAgreementServiceTask" targetRef="Participant_0y932pu" />
|
||||
<bpmn:messageFlow id="Flow_1ax0a41" sourceRef="ApproveLoanAgreementServiceTask" targetRef="Participant_0z5fkq5" />
|
||||
<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_1cojsk2" targetNamespace="http://bpmn.io/schema/bpmn" exporter="Camunda Modeler" exporterVersion="5.0.0" modeler:executionPlatform="Camunda Cloud" modeler:executionPlatformVersion="8.0.0">
|
||||
<bpmn:collaboration id="Collaboration_0w35pbw">
|
||||
<bpmn:participant id="Participant_16ksm4o" name="Loan Agreement example" processRef="Loan_Agreement" />
|
||||
<bpmn:participant id="Participant_1xcghje" name="Third-party-legacy System" />
|
||||
<bpmn:participant id="Participant_10a5hxf" name="Third-party-legacy System" />
|
||||
<bpmn:messageFlow id="Flow_1a4toos" sourceRef="ApproveLoanAgreementServiceTask" targetRef="Participant_1xcghje" />
|
||||
<bpmn:messageFlow id="Flow_1sr6eeg" sourceRef="RejectLoanAgreementServiceTask" targetRef="Participant_10a5hxf" />
|
||||
</bpmn:collaboration>
|
||||
<bpmn:process id="Loan_Agreement" name="Loan Agreement" isExecutable="true">
|
||||
<bpmn:businessRuleTask id="ApproveAgreementRuleTask" name="Approve agreement" camunda:resultVariable="approved" camunda:decisionRef="approvement-check" camunda:mapDecisionResult="singleEntry">
|
||||
<bpmn:extensionElements />
|
||||
<bpmn:incoming>Flow_1rrsueh</bpmn:incoming>
|
||||
<bpmn:outgoing>Flow_00ukhfv</bpmn:outgoing>
|
||||
</bpmn:businessRuleTask>
|
||||
<bpmn:exclusiveGateway id="IsApprovedGateway" name="Is agreement approved?">
|
||||
<bpmn:incoming>Flow_00ukhfv</bpmn:incoming>
|
||||
<bpmn:outgoing>Flow_0xpo6jp</bpmn:outgoing>
|
||||
<bpmn:outgoing>Flow_1hri7xc</bpmn:outgoing>
|
||||
</bpmn:exclusiveGateway>
|
||||
<bpmn:serviceTask id="ApproveLoanAgreementServiceTask" name="Approve loan agreement" camunda:delegateExpression="${approveLoanAgreement}">
|
||||
<bpmn:incoming>Flow_1hri7xc</bpmn:incoming>
|
||||
<bpmn:outgoing>Flow_1uqmps7</bpmn:outgoing>
|
||||
</bpmn:serviceTask>
|
||||
<bpmn:startEvent id="LoanAgreementReciedStartEvent" name="loan agreement recived" camunda:asyncAfter="true">
|
||||
<bpmn:outgoing>Flow_1rrsueh</bpmn:outgoing>
|
||||
<bpmn:messageEventDefinition id="MessageEventDefinition_1j9r08u" messageRef="Message_0o102df" />
|
||||
<bpmn:startEvent id="LoanAgreementReceivedStartEvent" name="loan agreement recived">
|
||||
<bpmn:outgoing>Flow_1a7dewt</bpmn:outgoing>
|
||||
<bpmn:messageEventDefinition id="MessageEventDefinition_1c5v0o8" messageRef="Message_0o9ohqs" />
|
||||
</bpmn:startEvent>
|
||||
<bpmn:sequenceFlow id="Flow_1uqmps7" sourceRef="ApproveLoanAgreementServiceTask" targetRef="SendCrossSellingEvent" />
|
||||
<bpmn:sequenceFlow id="Flow_1hri7xc" name="yes" sourceRef="IsApprovedGateway" targetRef="ApproveLoanAgreementServiceTask">
|
||||
<bpmn:conditionExpression xsi:type="bpmn:tFormalExpression">${approved}</bpmn:conditionExpression>
|
||||
</bpmn:sequenceFlow>
|
||||
<bpmn:sequenceFlow id="Flow_0xpo6jp" name="no" sourceRef="IsApprovedGateway" targetRef="RejectLoanAgreementServiceTask">
|
||||
<bpmn:conditionExpression xsi:type="bpmn:tFormalExpression">${!approved}</bpmn:conditionExpression>
|
||||
</bpmn:sequenceFlow>
|
||||
<bpmn:sequenceFlow id="Flow_00ukhfv" sourceRef="ApproveAgreementRuleTask" targetRef="IsApprovedGateway" />
|
||||
<bpmn:sequenceFlow id="Flow_1rrsueh" sourceRef="LoanAgreementReciedStartEvent" targetRef="ApproveAgreementRuleTask" />
|
||||
<bpmn:serviceTask id="RejectLoanAgreementServiceTask" name="Rejection loan agreement" camunda:delegateExpression="${rejectionLoanAgreement}">
|
||||
<bpmn:incoming>Flow_0xpo6jp</bpmn:incoming>
|
||||
<bpmn:outgoing>Flow_0eif63m</bpmn:outgoing>
|
||||
<bpmn:businessRuleTask id="ApproveAgreementRuleTask" name="Approve agreement">
|
||||
<bpmn:extensionElements>
|
||||
<zeebe:calledDecision decisionId="approvement-check" resultVariable="approved" />
|
||||
</bpmn:extensionElements>
|
||||
<bpmn:incoming>Flow_1a7dewt</bpmn:incoming>
|
||||
<bpmn:outgoing>Flow_0b8w6r4</bpmn:outgoing>
|
||||
</bpmn:businessRuleTask>
|
||||
<bpmn:exclusiveGateway id="Gateway_1lbkm1m" name="Is agreement approved?">
|
||||
<bpmn:incoming>Flow_0b8w6r4</bpmn:incoming>
|
||||
<bpmn:outgoing>Flow_11ck3o3</bpmn:outgoing>
|
||||
<bpmn:outgoing>Flow_1x7d6jb</bpmn:outgoing>
|
||||
</bpmn:exclusiveGateway>
|
||||
<bpmn:serviceTask id="ApproveLoanAgreementServiceTask" name="Approve loan agreement">
|
||||
<bpmn:extensionElements>
|
||||
<zeebe:taskDefinition type="approve-loan-agreement" />
|
||||
</bpmn:extensionElements>
|
||||
<bpmn:incoming>Flow_11ck3o3</bpmn:incoming>
|
||||
<bpmn:outgoing>Flow_1m68173</bpmn:outgoing>
|
||||
</bpmn:serviceTask>
|
||||
<bpmn:serviceTask id="RejectLoanAgreementServiceTask" name="Rejection loan agreement">
|
||||
<bpmn:extensionElements>
|
||||
<zeebe:taskDefinition type="reject-loan-agreement" />
|
||||
</bpmn:extensionElements>
|
||||
<bpmn:incoming>Flow_1x7d6jb</bpmn:incoming>
|
||||
<bpmn:outgoing>Flow_0u06ha5</bpmn:outgoing>
|
||||
</bpmn:serviceTask>
|
||||
<bpmn:sequenceFlow id="Flow_0eif63m" sourceRef="RejectLoanAgreementServiceTask" targetRef="LoanAgreementNotApprovedEndEvent" />
|
||||
<bpmn:endEvent id="LoanAgreementApprovedEndEvent" name="loan agreement approved">
|
||||
<bpmn:incoming>Flow_1pvaqlv</bpmn:incoming>
|
||||
</bpmn:endEvent>
|
||||
<bpmn:sequenceFlow id="Flow_1pvaqlv" sourceRef="SendCrossSellingEvent" targetRef="LoanAgreementApprovedEndEvent" />
|
||||
<bpmn:intermediateThrowEvent id="SendCrossSellingEvent" name="Send cross-selling recommendaiton">
|
||||
<bpmn:incoming>Flow_1uqmps7</bpmn:incoming>
|
||||
<bpmn:outgoing>Flow_1pvaqlv</bpmn:outgoing>
|
||||
<bpmn:messageEventDefinition id="MessageEventDefinition_0drw25h" camunda:delegateExpression="${sendCrossSellingRecommendation}" />
|
||||
</bpmn:intermediateThrowEvent>
|
||||
<bpmn:endEvent id="LoanAgreementNotApprovedEndEvent" name="loan agreement not approved">
|
||||
<bpmn:incoming>Flow_0eif63m</bpmn:incoming>
|
||||
<bpmn:incoming>Flow_0u06ha5</bpmn:incoming>
|
||||
</bpmn:endEvent>
|
||||
<bpmn:sequenceFlow id="Flow_1a7dewt" sourceRef="LoanAgreementReceivedStartEvent" targetRef="ApproveAgreementRuleTask" />
|
||||
<bpmn:sequenceFlow id="Flow_0b8w6r4" sourceRef="ApproveAgreementRuleTask" targetRef="Gateway_1lbkm1m" />
|
||||
<bpmn:sequenceFlow id="Flow_11ck3o3" name="yes" sourceRef="Gateway_1lbkm1m" targetRef="ApproveLoanAgreementServiceTask">
|
||||
<bpmn:conditionExpression xsi:type="bpmn:tFormalExpression">=approved=true</bpmn:conditionExpression>
|
||||
</bpmn:sequenceFlow>
|
||||
<bpmn:sequenceFlow id="Flow_1m68173" sourceRef="ApproveLoanAgreementServiceTask" targetRef="SendCrossSellingEvent" />
|
||||
<bpmn:sequenceFlow id="Flow_1x7d6jb" name="no" sourceRef="Gateway_1lbkm1m" targetRef="RejectLoanAgreementServiceTask">
|
||||
<bpmn:conditionExpression xsi:type="bpmn:tFormalExpression">=approved=false</bpmn:conditionExpression>
|
||||
</bpmn:sequenceFlow>
|
||||
<bpmn:sequenceFlow id="Flow_0u06ha5" sourceRef="RejectLoanAgreementServiceTask" targetRef="LoanAgreementNotApprovedEndEvent" />
|
||||
<bpmn:intermediateThrowEvent id="SendCrossSellingEvent" name="Send cross-selling recommendaiton">
|
||||
<bpmn:extensionElements>
|
||||
<zeebe:taskDefinition type="send-cross-selling-recommendation" />
|
||||
</bpmn:extensionElements>
|
||||
<bpmn:incoming>Flow_1m68173</bpmn:incoming>
|
||||
<bpmn:outgoing>Flow_04mwr0b</bpmn:outgoing>
|
||||
<bpmn:messageEventDefinition id="MessageEventDefinition_1x5iaae" />
|
||||
</bpmn:intermediateThrowEvent>
|
||||
<bpmn:endEvent id="LoanAgreementApprovedEndEvent" name="loan agreement approved">
|
||||
<bpmn:incoming>Flow_04mwr0b</bpmn:incoming>
|
||||
</bpmn:endEvent>
|
||||
<bpmn:sequenceFlow id="Flow_04mwr0b" sourceRef="SendCrossSellingEvent" targetRef="LoanAgreementApprovedEndEvent" />
|
||||
</bpmn:process>
|
||||
<bpmn:message id="Message_0o102df" name="loanAgreementReceivedMessage" />
|
||||
<bpmn:message id="Message_0o9ohqs" name="loanAgreementReceivedMessage" />
|
||||
<bpmndi:BPMNDiagram id="BPMNDiagram_1">
|
||||
<bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="Collaboration_0w4ikjr">
|
||||
<bpmndi:BPMNShape id="Participant_1p6ilgg_di" bpmnElement="Participant_1p6ilgg" isHorizontal="true">
|
||||
<dc:Bounds x="160" y="177" width="820" height="255" />
|
||||
<bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="Collaboration_0w35pbw">
|
||||
<bpmndi:BPMNShape id="Participant_16ksm4o_di" bpmnElement="Participant_16ksm4o" isHorizontal="true">
|
||||
<dc:Bounds x="160" y="170" width="860" height="270" />
|
||||
<bpmndi:BPMNLabel />
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNEdge id="Flow_1rrsueh_di" bpmnElement="Flow_1rrsueh">
|
||||
<di:waypoint x="258" y="237" />
|
||||
<di:waypoint x="330" y="237" />
|
||||
<bpmndi:BPMNEdge id="Flow_04mwr0b_di" bpmnElement="Flow_04mwr0b">
|
||||
<di:waypoint x="838" y="250" />
|
||||
<di:waypoint x="936" y="250" />
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNEdge id="Flow_00ukhfv_di" bpmnElement="Flow_00ukhfv">
|
||||
<di:waypoint x="430" y="237" />
|
||||
<di:waypoint x="485" y="237" />
|
||||
<bpmndi:BPMNEdge id="Flow_0u06ha5_di" bpmnElement="Flow_0u06ha5">
|
||||
<di:waypoint x="710" y="360" />
|
||||
<di:waypoint x="802" y="360" />
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNEdge id="Flow_0xpo6jp_di" bpmnElement="Flow_0xpo6jp">
|
||||
<di:waypoint x="510" y="262" />
|
||||
<di:waypoint x="510" y="360" />
|
||||
<di:waypoint x="600" y="360" />
|
||||
<bpmndi:BPMNEdge id="Flow_1x7d6jb_di" bpmnElement="Flow_1x7d6jb">
|
||||
<di:waypoint x="500" y="275" />
|
||||
<di:waypoint x="500" y="360" />
|
||||
<di:waypoint x="610" y="360" />
|
||||
<bpmndi:BPMNLabel>
|
||||
<dc:Bounds x="519" y="308" width="13" height="14" />
|
||||
<dc:Bounds x="509" y="315" width="13" height="14" />
|
||||
</bpmndi:BPMNLabel>
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNEdge id="Flow_1hri7xc_di" bpmnElement="Flow_1hri7xc">
|
||||
<di:waypoint x="535" y="237" />
|
||||
<di:waypoint x="600" y="237" />
|
||||
<bpmndi:BPMNEdge id="Flow_1m68173_di" bpmnElement="Flow_1m68173">
|
||||
<di:waypoint x="710" y="250" />
|
||||
<di:waypoint x="802" y="250" />
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNEdge id="Flow_11ck3o3_di" bpmnElement="Flow_11ck3o3">
|
||||
<di:waypoint x="525" y="250" />
|
||||
<di:waypoint x="610" y="250" />
|
||||
<bpmndi:BPMNLabel>
|
||||
<dc:Bounds x="559" y="219" width="18" height="14" />
|
||||
<dc:Bounds x="559" y="232" width="18" height="14" />
|
||||
</bpmndi:BPMNLabel>
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNEdge id="Flow_1uqmps7_di" bpmnElement="Flow_1uqmps7">
|
||||
<di:waypoint x="700" y="237" />
|
||||
<di:waypoint x="772" y="237" />
|
||||
<bpmndi:BPMNEdge id="Flow_0b8w6r4_di" bpmnElement="Flow_0b8w6r4">
|
||||
<di:waypoint x="420" y="250" />
|
||||
<di:waypoint x="475" y="250" />
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNEdge id="Flow_0eif63m_di" bpmnElement="Flow_0eif63m">
|
||||
<di:waypoint x="700" y="360" />
|
||||
<di:waypoint x="772" y="360" />
|
||||
<bpmndi:BPMNEdge id="Flow_1a7dewt_di" bpmnElement="Flow_1a7dewt">
|
||||
<di:waypoint x="268" y="250" />
|
||||
<di:waypoint x="320" y="250" />
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNEdge id="Flow_1pvaqlv_di" bpmnElement="Flow_1pvaqlv">
|
||||
<di:waypoint x="808" y="237" />
|
||||
<di:waypoint x="882" y="237" />
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNShape id="Activity_1244zx8_di" bpmnElement="ApproveAgreementRuleTask">
|
||||
<dc:Bounds x="330" y="197" width="100" height="80" />
|
||||
<bpmndi:BPMNShape id="Event_189g024_di" bpmnElement="LoanAgreementReceivedStartEvent">
|
||||
<dc:Bounds x="232" y="232" width="36" height="36" />
|
||||
<bpmndi:BPMNLabel>
|
||||
<dc:Bounds x="212" y="275" width="77" height="27" />
|
||||
</bpmndi:BPMNLabel>
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape id="Activity_061t2y6_di" bpmnElement="ApproveAgreementRuleTask">
|
||||
<dc:Bounds x="320" y="210" width="100" height="80" />
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape id="Gateway_1lbkm1m_di" bpmnElement="Gateway_1lbkm1m" isMarkerVisible="true">
|
||||
<dc:Bounds x="475" y="225" width="50" height="50" />
|
||||
<bpmndi:BPMNLabel>
|
||||
<dc:Bounds x="468" y="186" width="65" height="27" />
|
||||
</bpmndi:BPMNLabel>
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape id="Activity_12k9z5m_di" bpmnElement="ApproveLoanAgreementServiceTask">
|
||||
<dc:Bounds x="610" y="210" width="100" height="80" />
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape id="Activity_1to38de_di" bpmnElement="RejectLoanAgreementServiceTask">
|
||||
<dc:Bounds x="610" y="320" width="100" height="80" />
|
||||
<bpmndi:BPMNLabel />
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape id="Gateway_0xmpbgn_di" bpmnElement="IsApprovedGateway" isMarkerVisible="true">
|
||||
<dc:Bounds x="485" y="212" width="50" height="50" />
|
||||
<bpmndi:BPMNShape id="Event_1e7aj39_di" bpmnElement="LoanAgreementNotApprovedEndEvent">
|
||||
<dc:Bounds x="802" y="342" width="36" height="36" />
|
||||
<bpmndi:BPMNLabel>
|
||||
<dc:Bounds x="478" y="182" width="65" height="27" />
|
||||
<dc:Bounds x="782" y="385" width="77" height="27" />
|
||||
</bpmndi:BPMNLabel>
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape id="Activity_13yt0mp_di" bpmnElement="ApproveLoanAgreementServiceTask">
|
||||
<dc:Bounds x="600" y="197" width="100" height="80" />
|
||||
<bpmndi:BPMNShape id="Event_0b9mm01_di" bpmnElement="SendCrossSellingEvent">
|
||||
<dc:Bounds x="802" y="232" width="36" height="36" />
|
||||
<bpmndi:BPMNLabel>
|
||||
<dc:Bounds x="780" y="275" width="82" height="40" />
|
||||
</bpmndi:BPMNLabel>
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape id="BPMNShape_0ya5h8m" bpmnElement="LoanAgreementApprovedEndEvent">
|
||||
<dc:Bounds x="936" y="232" width="36" height="36" />
|
||||
<bpmndi:BPMNLabel>
|
||||
<dc:Bounds x="916" y="275" width="77" height="27" />
|
||||
</bpmndi:BPMNLabel>
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape id="Participant_1mw44p0_di" bpmnElement="Participant_1xcghje" isHorizontal="true">
|
||||
<dc:Bounds x="510" y="80" width="300" height="60" />
|
||||
<bpmndi:BPMNLabel />
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape id="Activity_1uvc9z4_di" bpmnElement="RejectLoanAgreementServiceTask">
|
||||
<dc:Bounds x="600" y="320" width="100" height="80" />
|
||||
<bpmndi:BPMNShape id="BPMNShape_0elm7s8" bpmnElement="Participant_10a5hxf" isHorizontal="true">
|
||||
<dc:Bounds x="510" y="470" width="300" height="60" />
|
||||
<bpmndi:BPMNLabel />
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape id="Event_086ty8n_di" bpmnElement="LoanAgreementReciedStartEvent">
|
||||
<dc:Bounds x="222" y="219" width="36" height="36" />
|
||||
<bpmndi:BPMNLabel>
|
||||
<dc:Bounds x="202" y="262" width="77" height="27" />
|
||||
</bpmndi:BPMNLabel>
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape id="Event_0mxjaa5_di" bpmnElement="LoanAgreementApprovedEndEvent">
|
||||
<dc:Bounds x="882" y="219" width="36" height="36" />
|
||||
<bpmndi:BPMNLabel>
|
||||
<dc:Bounds x="862" y="262" width="77" height="27" />
|
||||
</bpmndi:BPMNLabel>
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape id="Event_1hg5yw3_di" bpmnElement="SendCrossSellingEvent">
|
||||
<dc:Bounds x="772" y="219" width="36" height="36" />
|
||||
<bpmndi:BPMNLabel>
|
||||
<dc:Bounds x="750" y="262" width="82" height="40" />
|
||||
</bpmndi:BPMNLabel>
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape id="Event_1i3yo9j_di" bpmnElement="LoanAgreementNotApprovedEndEvent">
|
||||
<dc:Bounds x="772" y="342" width="36" height="36" />
|
||||
<bpmndi:BPMNLabel>
|
||||
<dc:Bounds x="752" y="385" width="77" height="27" />
|
||||
</bpmndi:BPMNLabel>
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape id="Participant_1s9mw1a_di" bpmnElement="Participant_0y932pu" isHorizontal="true">
|
||||
<dc:Bounds x="500" y="460" width="300" height="60" />
|
||||
<bpmndi:BPMNLabel />
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape id="BPMNShape_1ilsqo5" bpmnElement="Participant_0z5fkq5" isHorizontal="true">
|
||||
<dc:Bounds x="500" y="80" width="300" height="60" />
|
||||
<bpmndi:BPMNLabel />
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNEdge id="Flow_1ytdopc_di" bpmnElement="Flow_1ytdopc">
|
||||
<di:waypoint x="650" y="400" />
|
||||
<di:waypoint x="650" y="460" />
|
||||
<bpmndi:BPMNEdge id="Flow_1a4toos_di" bpmnElement="Flow_1a4toos">
|
||||
<di:waypoint x="660" y="210" />
|
||||
<di:waypoint x="660" y="140" />
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNEdge id="Flow_1ax0a41_di" bpmnElement="Flow_1ax0a41">
|
||||
<di:waypoint x="650" y="197" />
|
||||
<di:waypoint x="650" y="140" />
|
||||
<bpmndi:BPMNEdge id="Flow_1sr6eeg_di" bpmnElement="Flow_1sr6eeg">
|
||||
<di:waypoint x="660" y="400" />
|
||||
<di:waypoint x="660" y="470" />
|
||||
</bpmndi:BPMNEdge>
|
||||
</bpmndi:BPMNPlane>
|
||||
</bpmndi:BPMNDiagram>
|
||||
|
||||
@@ -1,40 +1,23 @@
|
||||
package de.weinbrecht.luc.bpm.architecture.loan.agreement;
|
||||
|
||||
import de.weinbrecht.luc.bpm.architecture.loan.agreement.adapter.in.process.ApproveLoanAgreement;
|
||||
import de.weinbrecht.luc.bpm.architecture.loan.agreement.adapter.in.process.RejectionLoanAgreement;
|
||||
import de.weinbrecht.luc.bpm.architecture.loan.agreement.adapter.in.process.SendCrossSellingRecommendation;
|
||||
import org.camunda.bpm.dmn.engine.DmnDecisionTableResult;
|
||||
import org.camunda.bpm.engine.runtime.ProcessInstance;
|
||||
import org.camunda.bpm.engine.test.Deployment;
|
||||
import org.camunda.bpm.extension.junit5.test.ProcessEngineExtension;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import io.camunda.zeebe.client.ZeebeClient;
|
||||
import io.camunda.zeebe.client.api.response.PublishMessageResponse;
|
||||
import io.camunda.zeebe.process.test.api.ZeebeTestEngine;
|
||||
import io.camunda.zeebe.process.test.extension.ZeebeProcessTest;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.Arguments;
|
||||
import org.junit.jupiter.params.provider.MethodSource;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.stream.Stream;
|
||||
import static de.weinbrecht.luc.bpm.architecture.ProcessTestUtils.*;
|
||||
import static de.weinbrecht.luc.bpm.architecture.loan.agreement.adapter.common.ProcessConstants.*;
|
||||
import static io.camunda.zeebe.process.test.assertions.BpmnAssert.assertThat;
|
||||
import static java.util.Collections.singletonMap;
|
||||
|
||||
import static de.weinbrecht.luc.bpm.architecture.loan.agreement.adapter.common.ProcessConstants.LOAN_AGREEMENT_NUMBER;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.entry;
|
||||
import static org.camunda.bpm.engine.test.assertions.bpmn.BpmnAwareTests.assertThat;
|
||||
import static org.camunda.bpm.engine.test.assertions.bpmn.BpmnAwareTests.decisionService;
|
||||
import static org.camunda.bpm.engine.test.assertions.bpmn.BpmnAwareTests.execute;
|
||||
import static org.camunda.bpm.engine.test.assertions.bpmn.BpmnAwareTests.job;
|
||||
import static org.camunda.bpm.engine.test.assertions.bpmn.BpmnAwareTests.runtimeService;
|
||||
import static org.camunda.bpm.engine.test.assertions.bpmn.BpmnAwareTests.withVariables;
|
||||
import static org.camunda.community.mockito.DelegateExpressions.registerJavaDelegateMock;
|
||||
import static org.camunda.community.mockito.DelegateExpressions.verifyJavaDelegateMock;
|
||||
|
||||
@ExtendWith(ProcessEngineExtension.class)
|
||||
@ZeebeProcessTest
|
||||
class ProcessTest {
|
||||
|
||||
public static final String PROCESS_DEFINITION = "Loan_Agreement";
|
||||
private ZeebeTestEngine engine;
|
||||
private ZeebeClient client;
|
||||
|
||||
private static final String START_EVENT = "LoanAgreementReciedStartEvent";
|
||||
private static final String START_EVENT = "LoanAgreementReceivedStartEvent";
|
||||
private static final String APPROVE_RULE_TASK = "ApproveAgreementRuleTask";
|
||||
private static final String APPROVE_AGREEMENT_SERVICE_TASK = "ApproveLoanAgreementServiceTask";
|
||||
private static final String SEND_CROSS_SELLING_EVENT = "SendCrossSellingEvent";
|
||||
@@ -43,84 +26,46 @@ class ProcessTest {
|
||||
private static final String REJECT_AGREEMENT_SERVICE_TASK = "RejectLoanAgreementServiceTask";
|
||||
private static final String NOT_APPROVED_END_EVENT = "LoanAgreementNotApprovedEndEvent";
|
||||
|
||||
private static final String DMN_DEFINITION = "approvement-check";
|
||||
@Test
|
||||
void testRunsHappyPath() throws Exception {
|
||||
deployResources(client, "loan_agreement.bpmn", "approve_agreement.dmn");
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
registerJavaDelegateMock(ApproveLoanAgreement.class);
|
||||
registerJavaDelegateMock(RejectionLoanAgreement.class);
|
||||
registerJavaDelegateMock(SendCrossSellingRecommendation.class);
|
||||
final PublishMessageResponse response = sendMessage(engine, client,
|
||||
LOAN_START_EVENT_MESSAGE_REF, "", singletonMap(LOAN_AGREEMENT_NUMBER, 1L));
|
||||
|
||||
assertThat(response).extractingProcessInstance()
|
||||
.hasPassedElementsInOrder(START_EVENT, APPROVE_RULE_TASK)
|
||||
.isWaitingAtElements(APPROVE_AGREEMENT_SERVICE_TASK);
|
||||
|
||||
completeTaskWithType(engine , client, APPROVE_AGREEMENT_SERVICE_TASK, LOAN_AGREEMENT_TASK);
|
||||
|
||||
assertThat(response).extractingProcessInstance()
|
||||
.hasPassedElement(START_EVENT)
|
||||
.isWaitingAtElements(SEND_CROSS_SELLING_EVENT);
|
||||
|
||||
completeTaskWithType(engine , client, SEND_CROSS_SELLING_EVENT, SEND_CROSS_SELLING_RECOMMENDATION_TASK);
|
||||
|
||||
assertThat(response).extractingProcessInstance()
|
||||
.hasPassedElementsInOrder(SEND_CROSS_SELLING_EVENT, APPROVED_END_EVENT)
|
||||
.isCompleted();
|
||||
}
|
||||
|
||||
@Test
|
||||
@Deployment(resources = { "loan_agreement.bpmn", "approve_agreement.dmn"})
|
||||
void shouldExecuteProcess_happy_path() {
|
||||
ProcessInstance processInstance = runtimeService().startProcessInstanceByKey(
|
||||
PROCESS_DEFINITION,
|
||||
withVariables(LOAN_AGREEMENT_NUMBER, 1L)
|
||||
);
|
||||
assertThat(processInstance).isActive();
|
||||
void testRunsExceptionPath() throws Exception {
|
||||
deployResources(client, "loan_agreement.bpmn", "approve_agreement.dmn");
|
||||
|
||||
assertThat(processInstance).isWaitingAt(START_EVENT);
|
||||
final PublishMessageResponse response = sendMessage(engine, client,
|
||||
LOAN_START_EVENT_MESSAGE_REF, "", singletonMap(LOAN_AGREEMENT_NUMBER, 6L));
|
||||
|
||||
execute(job());
|
||||
assertThat(response).extractingProcessInstance()
|
||||
.hasPassedElementsInOrder(START_EVENT, APPROVE_RULE_TASK)
|
||||
.isWaitingAtElements(REJECT_AGREEMENT_SERVICE_TASK);
|
||||
|
||||
assertThat(processInstance)
|
||||
.hasPassed(START_EVENT,
|
||||
APPROVE_RULE_TASK,
|
||||
APPROVE_AGREEMENT_SERVICE_TASK,
|
||||
SEND_CROSS_SELLING_EVENT,
|
||||
APPROVED_END_EVENT);
|
||||
completeTaskWithType(engine , client, REJECT_AGREEMENT_SERVICE_TASK, LOAN_REJECTION_TASK);
|
||||
|
||||
verifyJavaDelegateMock(SendCrossSellingRecommendation.class).executed();
|
||||
verifyJavaDelegateMock(ApproveLoanAgreement.class).executed();
|
||||
|
||||
assertThat(processInstance).isEnded();
|
||||
}
|
||||
|
||||
@Test
|
||||
@Deployment(resources = { "loan_agreement.bpmn", "approve_agreement.dmn"})
|
||||
void shouldExecuteProcess_exceptional_path() {
|
||||
ProcessInstance processInstance = runtimeService().startProcessInstanceByKey(
|
||||
PROCESS_DEFINITION,
|
||||
withVariables(LOAN_AGREEMENT_NUMBER, 8L)
|
||||
);
|
||||
assertThat(processInstance).isActive();
|
||||
|
||||
assertThat(processInstance).isWaitingAt(START_EVENT);
|
||||
|
||||
execute(job());
|
||||
|
||||
assertThat(processInstance)
|
||||
.hasPassed(START_EVENT,
|
||||
APPROVE_RULE_TASK,
|
||||
REJECT_AGREEMENT_SERVICE_TASK,
|
||||
NOT_APPROVED_END_EVENT);
|
||||
|
||||
verifyJavaDelegateMock(RejectionLoanAgreement.class).executed();
|
||||
|
||||
assertThat(processInstance).isEnded();
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("provideProcessVariablesForDMN")
|
||||
@Deployment(resources = "approve_agreement.dmn")
|
||||
void testApprovementDMN(Long input, boolean expected) {
|
||||
Map<String, Object> variables = withVariables(LOAN_AGREEMENT_NUMBER, input);
|
||||
|
||||
DmnDecisionTableResult tableResult = decisionService().evaluateDecisionTableByKey(DMN_DEFINITION, variables);
|
||||
|
||||
assertThat(tableResult.getFirstResult()).contains(entry("approved", expected));
|
||||
}
|
||||
|
||||
private static Stream<Arguments> provideProcessVariablesForDMN() {
|
||||
return Stream.of(
|
||||
Arguments.of(1L, true),
|
||||
Arguments.of(2L, true),
|
||||
Arguments.of(3L, true),
|
||||
Arguments.of(4L, true),
|
||||
Arguments.of(5L, false),
|
||||
Arguments.of(6L, false)
|
||||
);
|
||||
assertThat(response).extractingProcessInstance()
|
||||
.hasPassedElementsInOrder(NOT_APPROVED_END_EVENT)
|
||||
.hasNotPassedElement(SEND_CROSS_SELLING_EVENT)
|
||||
.isCompleted();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,130 @@
|
||||
package de.weinbrecht.luc.bpm.architecture.loan.agreement;
|
||||
|
||||
import de.weinbrecht.luc.bpm.architecture.loan.agreement.domain.model.*;
|
||||
import de.weinbrecht.luc.bpm.architecture.loan.agreement.domain.model.recipient.MailAddress;
|
||||
import de.weinbrecht.luc.bpm.architecture.loan.agreement.domain.model.recipient.Name;
|
||||
import de.weinbrecht.luc.bpm.architecture.loan.agreement.domain.model.recipient.Recipient;
|
||||
import de.weinbrecht.luc.bpm.architecture.loan.agreement.usecase.in.LoanAgreementCreation;
|
||||
import de.weinbrecht.luc.bpm.architecture.loan.agreement.usecase.in.LoanAgreementStatusCommand;
|
||||
import de.weinbrecht.luc.bpm.architecture.loan.agreement.usecase.out.LoanAgreementCommand;
|
||||
import de.weinbrecht.luc.bpm.architecture.loan.agreement.usecase.out.LoanAgreementQuery;
|
||||
import de.weinbrecht.luc.bpm.architecture.loan.agreement.usecase.out.RecommendationTrigger;
|
||||
import io.camunda.zeebe.client.ZeebeClient;
|
||||
import io.camunda.zeebe.process.test.api.ZeebeTestEngine;
|
||||
import io.camunda.zeebe.spring.test.ZeebeSpringTest;
|
||||
import org.junit.jupiter.api.Disabled;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.boot.test.mock.mockito.MockBean;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import static de.weinbrecht.luc.bpm.architecture.SpringProcessTestUtils.*;
|
||||
import static de.weinbrecht.luc.bpm.architecture.loan.agreement.adapter.common.ProcessConstants.*;
|
||||
import static io.camunda.zeebe.process.test.filters.RecordStream.of;
|
||||
import static java.time.Duration.ofSeconds;
|
||||
import static java.util.Collections.singletonMap;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
// https://github.com/camunda-community-hub/camunda-8-examples/blob/main/twitter-review-java-springboot/src/test/java/org/camunda/community/examples/twitter/TestTwitterProcess.java
|
||||
@SpringBootTest
|
||||
@ZeebeSpringTest
|
||||
@Disabled("Flaky if runs together with the non Spring Process Tests")
|
||||
@MockBean({LoanAgreementCommand.class, LoanAgreementCreation.class})
|
||||
class SpringProcessTest {
|
||||
|
||||
@Autowired
|
||||
private ZeebeClient zeebe;
|
||||
|
||||
@Autowired
|
||||
private ZeebeTestEngine zeebeTestEngine;
|
||||
|
||||
@MockBean
|
||||
private LoanAgreementStatusCommand loanAgreementStatusCommand;
|
||||
|
||||
@MockBean
|
||||
private RecommendationTrigger recommendationTrigger;
|
||||
|
||||
@MockBean
|
||||
private LoanAgreementQuery loanAgreementQuery;
|
||||
|
||||
public static final String PROCESS_DEFINITION = "Loan_Agreement";
|
||||
private static final String START_EVENT = "LoanAgreementReceivedStartEvent";
|
||||
private static final String APPROVE_RULE_TASK = "ApproveAgreementRuleTask";
|
||||
private static final String APPROVE_AGREEMENT_SERVICE_TASK = "ApproveLoanAgreementServiceTask";
|
||||
private static final String SEND_CROSS_SELLING_EVENT = "SendCrossSellingEvent";
|
||||
private static final String APPROVED_END_EVENT = "LoanAgreementApprovedEndEvent";
|
||||
|
||||
private static final String REJECT_AGREEMENT_SERVICE_TASK = "RejectLoanAgreementServiceTask";
|
||||
private static final String NOT_APPROVED_END_EVENT = "LoanAgreementNotApprovedEndEvent";
|
||||
|
||||
@Test
|
||||
void testRunsProcessHappyPath() throws Exception {
|
||||
final LoanAgreementNumber loanAgreementNumber = new LoanAgreementNumber(1L);
|
||||
final CaseId caseId = new CaseId("Test-A-B");
|
||||
LoanAgreement loanAgreement = new LoanAgreement(
|
||||
loanAgreementNumber,
|
||||
new Recipient(
|
||||
new CustomerNumber("Test-1"),
|
||||
new Name("Tester"),
|
||||
new MailAddress("tester@web.io")
|
||||
),
|
||||
new Amount(100)
|
||||
);
|
||||
when(loanAgreementQuery.loadByNumber(loanAgreementNumber)).thenReturn(loanAgreement);
|
||||
|
||||
Map<String, Object> processVariables = new HashMap<>();
|
||||
processVariables.put(LOAN_AGREEMENT_NUMBER, loanAgreementNumber.getValue());
|
||||
processVariables.put(BUSINESS_KEY, caseId.getValue());
|
||||
zeebe.newPublishMessageCommand().messageName(LOAN_START_EVENT_MESSAGE_REF)
|
||||
.correlationKey("")
|
||||
.variables(processVariables)
|
||||
.send()
|
||||
.join();
|
||||
|
||||
hasPassedElement(START_EVENT, of(zeebeTestEngine.getRecordStreamSource()), PROCESS_DEFINITION);
|
||||
|
||||
zeebeTestEngine.waitForIdleState(ofSeconds(5));
|
||||
hasPassedElement(APPROVE_RULE_TASK, of(zeebeTestEngine.getRecordStreamSource()), PROCESS_DEFINITION);
|
||||
|
||||
waitForTaskAndComplete(zeebeTestEngine, zeebe, APPROVE_AGREEMENT_SERVICE_TASK, LOAN_AGREEMENT_TASK);
|
||||
zeebeTestEngine.waitForIdleState(ofSeconds(5));
|
||||
verify(loanAgreementStatusCommand).accept(loanAgreementNumber);
|
||||
|
||||
waitForTaskAndComplete(zeebeTestEngine, zeebe, SEND_CROSS_SELLING_EVENT, SEND_CROSS_SELLING_RECOMMENDATION_TASK);
|
||||
verify(recommendationTrigger).startLoanAgreement(caseId, loanAgreement);
|
||||
|
||||
hasPassedElement(APPROVED_END_EVENT, of(zeebeTestEngine.getRecordStreamSource()), PROCESS_DEFINITION);
|
||||
|
||||
assertTrue(isProcessInstanceCompleted(of(zeebeTestEngine.getRecordStreamSource()), PROCESS_DEFINITION));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testRunsProcessExceptionalPath() throws Exception {
|
||||
final LoanAgreementNumber loanAgreementNumber = new LoanAgreementNumber(6L);
|
||||
|
||||
zeebe.newPublishMessageCommand().messageName(LOAN_START_EVENT_MESSAGE_REF)
|
||||
.correlationKey("")
|
||||
.variables(singletonMap(LOAN_AGREEMENT_NUMBER, loanAgreementNumber.getValue()))
|
||||
.send()
|
||||
.join();
|
||||
zeebeTestEngine.waitForIdleState(ofSeconds(5));
|
||||
|
||||
hasPassedElement(START_EVENT, of(zeebeTestEngine.getRecordStreamSource()), PROCESS_DEFINITION);
|
||||
|
||||
zeebeTestEngine.waitForIdleState(ofSeconds(5));
|
||||
hasPassedElement(APPROVE_RULE_TASK, of(zeebeTestEngine.getRecordStreamSource()), PROCESS_DEFINITION);
|
||||
|
||||
waitForTaskAndComplete(zeebeTestEngine, zeebe, REJECT_AGREEMENT_SERVICE_TASK, LOAN_REJECTION_TASK);
|
||||
verify(loanAgreementStatusCommand).reject(loanAgreementNumber);
|
||||
|
||||
zeebeTestEngine.waitForIdleState(ofSeconds(5));
|
||||
hasPassedElement(NOT_APPROVED_END_EVENT, of(zeebeTestEngine.getRecordStreamSource()), PROCESS_DEFINITION);
|
||||
|
||||
assertTrue(isProcessInstanceCompleted(of(zeebeTestEngine.getRecordStreamSource()), PROCESS_DEFINITION));
|
||||
}
|
||||
}
|
||||
@@ -2,14 +2,12 @@ package de.weinbrecht.luc.bpm.architecture.loan.agreement.adapter.in.process;
|
||||
|
||||
import de.weinbrecht.luc.bpm.architecture.loan.agreement.domain.model.LoanAgreementNumber;
|
||||
import de.weinbrecht.luc.bpm.architecture.loan.agreement.usecase.in.LoanAgreementStatusCommand;
|
||||
import org.camunda.bpm.engine.delegate.DelegateExecution;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoSettings;
|
||||
|
||||
import static de.weinbrecht.luc.bpm.architecture.loan.agreement.adapter.common.ProcessConstants.LOAN_AGREEMENT_NUMBER;
|
||||
import static org.mockito.Mockito.*;
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
||||
@MockitoSettings
|
||||
class ApproveLoanAgreementTest {
|
||||
@@ -22,10 +20,8 @@ class ApproveLoanAgreementTest {
|
||||
@Test
|
||||
void should_call_command_and_reject() {
|
||||
LoanAgreementNumber loanAgreementNumber = new LoanAgreementNumber(1L);
|
||||
DelegateExecution delegateExecution = mock(DelegateExecution.class);
|
||||
when(delegateExecution.getVariable(LOAN_AGREEMENT_NUMBER)).thenReturn(loanAgreementNumber.getValue());
|
||||
|
||||
classUnderTest.execute(delegateExecution);
|
||||
classUnderTest.handleJobFoo(loanAgreementNumber.getValue());
|
||||
|
||||
verify(loanAgreementStatusCommand).accept(loanAgreementNumber);
|
||||
}
|
||||
|
||||
@@ -2,14 +2,12 @@ package de.weinbrecht.luc.bpm.architecture.loan.agreement.adapter.in.process;
|
||||
|
||||
import de.weinbrecht.luc.bpm.architecture.loan.agreement.domain.model.LoanAgreementNumber;
|
||||
import de.weinbrecht.luc.bpm.architecture.loan.agreement.usecase.in.LoanAgreementStatusCommand;
|
||||
import org.camunda.bpm.engine.delegate.DelegateExecution;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoSettings;
|
||||
|
||||
import static de.weinbrecht.luc.bpm.architecture.loan.agreement.adapter.common.ProcessConstants.LOAN_AGREEMENT_NUMBER;
|
||||
import static org.mockito.Mockito.*;
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
||||
@MockitoSettings
|
||||
class RejectionLoanAgreementTest {
|
||||
@@ -23,10 +21,8 @@ class RejectionLoanAgreementTest {
|
||||
@Test
|
||||
void should_call_command_and_reject() {
|
||||
LoanAgreementNumber loanAgreementNumber = new LoanAgreementNumber(1L);
|
||||
DelegateExecution delegateExecution = mock(DelegateExecution.class);
|
||||
when(delegateExecution.getVariable(LOAN_AGREEMENT_NUMBER)).thenReturn(loanAgreementNumber.getValue());
|
||||
|
||||
classUnderTest.execute(delegateExecution);
|
||||
classUnderTest.handleJobFoo(loanAgreementNumber.getValue());
|
||||
|
||||
verify(loanAgreementStatusCommand).reject(loanAgreementNumber);
|
||||
}
|
||||
|
||||
@@ -4,15 +4,14 @@ import de.weinbrecht.luc.bpm.architecture.loan.agreement.domain.model.CaseId;
|
||||
import de.weinbrecht.luc.bpm.architecture.loan.agreement.domain.model.LoanAgreement;
|
||||
import de.weinbrecht.luc.bpm.architecture.loan.agreement.usecase.out.LoanAgreementQuery;
|
||||
import de.weinbrecht.luc.bpm.architecture.loan.agreement.usecase.out.RecommendationTrigger;
|
||||
import org.camunda.bpm.engine.delegate.DelegateExecution;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoSettings;
|
||||
|
||||
import static de.weinbrecht.luc.bpm.architecture.loan.agreement.adapter.common.ProcessConstants.LOAN_AGREEMENT_NUMBER;
|
||||
import static de.weinbrecht.luc.bpm.architecture.loan.agreement.domain.model.TestdataGenerator.createLoanAgreementWithNumber;
|
||||
import static org.mockito.Mockito.*;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@MockitoSettings
|
||||
class SendCrossSellingRecommendationTest {
|
||||
@@ -29,15 +28,11 @@ class SendCrossSellingRecommendationTest {
|
||||
@Test
|
||||
void should_load_data_and_start_process_by_message() {
|
||||
LoanAgreement loanAgreement = createLoanAgreementWithNumber();
|
||||
String caseId = "11";
|
||||
DelegateExecution delegateExecution = mock(DelegateExecution.class);
|
||||
when(delegateExecution.getBusinessKey()).thenReturn(caseId);
|
||||
when(delegateExecution.getVariable(LOAN_AGREEMENT_NUMBER))
|
||||
.thenReturn(loanAgreement.getLoanAgreementNumber().getValue());
|
||||
CaseId caseId = new CaseId("11");
|
||||
when(loanAgreementQuery.loadByNumber(loanAgreement.getLoanAgreementNumber())).thenReturn(loanAgreement);
|
||||
|
||||
classUnderTest.execute(delegateExecution);
|
||||
classUnderTest.handleJobFoo(loanAgreement.getLoanAgreementNumber().getValue(), caseId.getValue());
|
||||
|
||||
verify(recommendationTrigger).startLoanAgreement(new CaseId(caseId), loanAgreement);
|
||||
verify(recommendationTrigger).startLoanAgreement(caseId, loanAgreement);
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,7 @@ package de.weinbrecht.luc.bpm.architecture.loan.agreement.adapter.out.process;
|
||||
|
||||
import de.weinbrecht.luc.bpm.architecture.loan.agreement.domain.model.CaseId;
|
||||
import de.weinbrecht.luc.bpm.architecture.loan.agreement.domain.model.LoanAgreementNumber;
|
||||
import org.camunda.bpm.engine.RuntimeService;
|
||||
import io.camunda.zeebe.client.ZeebeClient;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
@@ -11,8 +11,8 @@ import org.mockito.junit.jupiter.MockitoSettings;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import static de.weinbrecht.luc.bpm.architecture.loan.agreement.adapter.common.ProcessConstants.LOAN_AGREEMENT_NUMBER;
|
||||
import static de.weinbrecht.luc.bpm.architecture.loan.agreement.adapter.common.ProcessConstants.LOAN_START_EVENT_MESSAGE_REF;
|
||||
import static de.weinbrecht.luc.bpm.architecture.loan.agreement.adapter.common.ProcessConstants.*;
|
||||
import static org.mockito.Answers.RETURNS_DEEP_STUBS;
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
||||
@MockitoSettings
|
||||
@@ -21,19 +21,25 @@ class ProcessEngineClientTest {
|
||||
@InjectMocks
|
||||
private ProcessEngineClient classUnderTest;
|
||||
|
||||
@Mock
|
||||
private RuntimeService runtimeService;
|
||||
@Mock(answer = RETURNS_DEEP_STUBS)
|
||||
private ZeebeClient client;
|
||||
|
||||
private final CaseId caseId = new CaseId("11");
|
||||
private final LoanAgreementNumber loanAgreementNumber = new LoanAgreementNumber(1L);
|
||||
|
||||
@Test
|
||||
void should_class_runtime_service_to_start() {
|
||||
void should_call_zeebe_to_start_reommendation() {
|
||||
Map<String, Object> processVariables = new HashMap<>();
|
||||
processVariables.put(LOAN_AGREEMENT_NUMBER, loanAgreementNumber.getValue());
|
||||
processVariables.put(BUSINESS_KEY, caseId.getValue());
|
||||
|
||||
classUnderTest.startLoanAgreement(caseId, loanAgreementNumber);
|
||||
|
||||
verify(runtimeService).startProcessInstanceByMessage(LOAN_START_EVENT_MESSAGE_REF, caseId.getValue(), processVariables);
|
||||
verify(client
|
||||
.newPublishMessageCommand()
|
||||
.messageName(LOAN_START_EVENT_MESSAGE_REF)
|
||||
.correlationKey("")
|
||||
.variables(processVariables)
|
||||
).send();
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1 @@
|
||||
spring.datasource.url: jdbc:h2:file:./camunda-h2-database
|
||||
|
||||
camunda.bpm.admin-user:
|
||||
id: admin
|
||||
password: pw
|
||||
@@ -1,22 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<beans xmlns="http://www.springframework.org/schema/beans"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
|
||||
|
||||
<bean id="processEngineConfiguration" class="org.camunda.bpm.engine.impl.cfg.StandaloneInMemProcessEngineConfiguration">
|
||||
|
||||
<property name="jdbcUrl" value="jdbc:h2:mem:camunda;DB_CLOSE_DELAY=1000" />
|
||||
<property name="jdbcDriver" value="org.h2.Driver" />
|
||||
<property name="jdbcUsername" value="sa" />
|
||||
<property name="jdbcPassword" value="" />
|
||||
|
||||
<!-- Database configurations -->
|
||||
<property name="databaseSchemaUpdate" value="true" />
|
||||
|
||||
<!-- job executor configurations -->
|
||||
<property name="jobExecutorActivate" value="false" />
|
||||
|
||||
<property name="history" value="full" />
|
||||
</bean>
|
||||
</beans>
|
||||
@@ -15,7 +15,7 @@
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
|
||||
<version.springboot>2.6.4</version.springboot>
|
||||
<version.camunda>7.17.0</version.camunda>
|
||||
<version.zeebe>8.0.1</version.zeebe>
|
||||
<version.junit5>5.8.2</version.junit5>
|
||||
<version.lombok>1.18.24</version.lombok>
|
||||
<version.domainprimitives>0.1.0</version.domainprimitives>
|
||||
@@ -36,9 +36,9 @@
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.camunda.bpm</groupId>
|
||||
<artifactId>camunda-bom</artifactId>
|
||||
<version>${version.camunda}</version>
|
||||
<groupId>io.camunda</groupId>
|
||||
<artifactId>zeebe-bom</artifactId>
|
||||
<version>${version.zeebe}</version>
|
||||
<scope>import</scope>
|
||||
<type>pom</type>
|
||||
</dependency>
|
||||
@@ -47,18 +47,35 @@
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.camunda.bpm.springboot</groupId>
|
||||
<artifactId>camunda-bpm-spring-boot-starter-rest</artifactId>
|
||||
<groupId>io.camunda</groupId>
|
||||
<artifactId>spring-zeebe</artifactId>
|
||||
<version>${version.zeebe}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.camunda.bpm.springboot</groupId>
|
||||
<artifactId>camunda-bpm-spring-boot-starter-webapp</artifactId>
|
||||
<groupId>io.camunda</groupId>
|
||||
<artifactId>spring-zeebe-starter</artifactId>
|
||||
<version>${version.zeebe}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.camunda.bpm.springboot</groupId>
|
||||
<artifactId>camunda-bpm-spring-boot-starter-test</artifactId>
|
||||
<groupId>io.camunda</groupId>
|
||||
<artifactId>spring-zeebe-util</artifactId>
|
||||
<version>${version.zeebe}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.camunda</groupId>
|
||||
<artifactId>spring-zeebe-test</artifactId>
|
||||
<version>${version.zeebe}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.camunda</groupId>
|
||||
<artifactId>zeebe-process-test-extension</artifactId>
|
||||
<version>${version.zeebe}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
@@ -113,20 +130,6 @@
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.camunda.bpm.extension</groupId>
|
||||
<artifactId>camunda-bpm-junit5</artifactId>
|
||||
<version>${version.bpmAssert}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.camunda.community.mockito</groupId>
|
||||
<artifactId>camunda-platform-7-mockito</artifactId>
|
||||
<version>${version.camundaMockito}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.tngtech.archunit</groupId>
|
||||
<artifactId>archunit-junit5</artifactId>
|
||||
@@ -146,6 +149,14 @@
|
||||
|
||||
<build>
|
||||
<finalName>${project.artifactId}</finalName>
|
||||
<testResources>
|
||||
<testResource>
|
||||
<directory>${project.basedir}/src/test/resources</directory>
|
||||
</testResource>
|
||||
<testResource>
|
||||
<directory>${project.basedir}/src/main/resources</directory>
|
||||
</testResource>
|
||||
</testResources>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
package de.weinbrecht.luc.bpm.architecture.recommendation;
|
||||
|
||||
import io.camunda.zeebe.spring.client.EnableZeebeClient;
|
||||
import io.camunda.zeebe.spring.client.annotation.ZeebeDeployment;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
@Configuration
|
||||
@EnableZeebeClient
|
||||
@ZeebeDeployment(resources = {"classpath:*.bpmn", "classpath:*.dmn"})
|
||||
public class ApplicationConfiguration {
|
||||
}
|
||||
@@ -5,4 +5,8 @@ public class ProcessConstants {
|
||||
|
||||
public static final String CUSTOMER_NUMBER = "customerNumber";
|
||||
public static final String CONTENT_NUMBER = "contentNumber";
|
||||
|
||||
|
||||
public static final String PICK_CONTENT_TASK = "pick-content";
|
||||
public static final String SEND_RECOMMENDATION_TASK = "send-recommendation";
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package de.weinbrecht.luc.bpm.architecture.recommendation.adapter.in.process;
|
||||
|
||||
import io.camunda.zeebe.client.api.response.ActivatedJob;
|
||||
|
||||
import static java.lang.String.format;
|
||||
|
||||
public class CouldNotCompleteJobException extends RuntimeException {
|
||||
public CouldNotCompleteJobException(final ActivatedJob job, Throwable cause) {
|
||||
super(format("Could not complete job %s", job), cause);
|
||||
}
|
||||
}
|
||||
@@ -2,22 +2,35 @@ package de.weinbrecht.luc.bpm.architecture.recommendation.adapter.in.process;
|
||||
|
||||
import de.weinbrecht.luc.bpm.architecture.recommendation.domain.model.ContentId;
|
||||
import de.weinbrecht.luc.bpm.architecture.recommendation.usecase.in.RecommendationPicker;
|
||||
import io.camunda.zeebe.client.api.response.ActivatedJob;
|
||||
import io.camunda.zeebe.client.api.worker.JobClient;
|
||||
import io.camunda.zeebe.spring.client.annotation.ZeebeWorker;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.camunda.bpm.engine.delegate.DelegateExecution;
|
||||
import org.camunda.bpm.engine.delegate.JavaDelegate;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import static de.weinbrecht.luc.bpm.architecture.recommendation.adapter.common.ProcessConstants.CONTENT_NUMBER;
|
||||
import static de.weinbrecht.luc.bpm.architecture.recommendation.adapter.common.ProcessConstants.PICK_CONTENT_TASK;
|
||||
|
||||
@RequiredArgsConstructor
|
||||
@Component
|
||||
public class PickContent implements JavaDelegate {
|
||||
public class PickContent {
|
||||
|
||||
private final RecommendationPicker recommendationPicker;
|
||||
|
||||
@Override
|
||||
public void execute(DelegateExecution execution) {
|
||||
@ZeebeWorker(type = PICK_CONTENT_TASK)
|
||||
public void handleJobFoo(final JobClient client, final ActivatedJob job) {
|
||||
ContentId contentId = recommendationPicker.pickContent();
|
||||
execution.setVariable(CONTENT_NUMBER, contentId.getValue());
|
||||
Map<String, Object> variables = new HashMap<>();
|
||||
variables.put(CONTENT_NUMBER, contentId.getValue());
|
||||
|
||||
client.newCompleteCommand(job.getKey())
|
||||
.variables(variables)
|
||||
.send()
|
||||
.exceptionally( throwable -> {
|
||||
throw new CouldNotCompleteJobException(job, throwable);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,27 +7,24 @@ import de.weinbrecht.luc.bpm.architecture.recommendation.domain.model.customer.C
|
||||
import de.weinbrecht.luc.bpm.architecture.recommendation.domain.model.customer.CustomerId;
|
||||
import de.weinbrecht.luc.bpm.architecture.recommendation.usecase.out.RecommendationQuery;
|
||||
import de.weinbrecht.luc.bpm.architecture.recommendation.usecase.out.SendNotification;
|
||||
import io.camunda.zeebe.spring.client.annotation.ZeebeVariable;
|
||||
import io.camunda.zeebe.spring.client.annotation.ZeebeWorker;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.camunda.bpm.engine.delegate.DelegateExecution;
|
||||
import org.camunda.bpm.engine.delegate.JavaDelegate;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import static de.weinbrecht.luc.bpm.architecture.recommendation.adapter.common.ProcessConstants.CONTENT_NUMBER;
|
||||
import static de.weinbrecht.luc.bpm.architecture.recommendation.adapter.common.ProcessConstants.CUSTOMER_NUMBER;
|
||||
import static de.weinbrecht.luc.bpm.architecture.recommendation.adapter.common.ProcessConstants.SEND_RECOMMENDATION_TASK;
|
||||
|
||||
@RequiredArgsConstructor
|
||||
@Component
|
||||
public class SendRecommendation implements JavaDelegate {
|
||||
public class SendRecommendation {
|
||||
|
||||
private final SendNotification sendNotification;
|
||||
private final RecommendationQuery recommendationQuery;
|
||||
|
||||
@Override
|
||||
public void execute(DelegateExecution execution) {
|
||||
Long contentId = (Long) execution.getVariable(CONTENT_NUMBER);
|
||||
String customerId = (String) execution.getVariable(CUSTOMER_NUMBER);
|
||||
Content content = recommendationQuery.findContentById(new ContentId(contentId));
|
||||
Customer customer = recommendationQuery.findCustomerById(new CustomerId(customerId));
|
||||
@ZeebeWorker(type = SEND_RECOMMENDATION_TASK, autoComplete = true)
|
||||
public void handleJobFoo(@ZeebeVariable Number contentNumber, @ZeebeVariable String customerNumber) {
|
||||
Content content = recommendationQuery.findContentById(new ContentId(contentNumber.longValue()));
|
||||
Customer customer = recommendationQuery.findCustomerById(new CustomerId(customerNumber));
|
||||
|
||||
Recommendation recommendation = new Recommendation(customer, content);
|
||||
|
||||
|
||||
@@ -2,10 +2,12 @@ package de.weinbrecht.luc.bpm.architecture.recommendation.adapter.out.db.content
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.boot.CommandLineRunner;
|
||||
import org.springframework.context.annotation.Profile;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@RequiredArgsConstructor
|
||||
@Component
|
||||
@Profile("!test")
|
||||
public class ExampleContentRunner implements CommandLineRunner {
|
||||
|
||||
private final ContentCRUDRepository contentCRUDRepository;
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
package de.weinbrecht.luc.bpm.architecture.recommendation.adapter.out.process;
|
||||
|
||||
public class CouldNotPublishMessageException extends RuntimeException {
|
||||
public CouldNotPublishMessageException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
}
|
||||
@@ -2,26 +2,32 @@ package de.weinbrecht.luc.bpm.architecture.recommendation.adapter.out.process;
|
||||
|
||||
import de.weinbrecht.luc.bpm.architecture.recommendation.domain.model.customer.CustomerId;
|
||||
import de.weinbrecht.luc.bpm.architecture.recommendation.usecase.out.StartRecommendation;
|
||||
import io.camunda.zeebe.client.ZeebeClient;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.camunda.bpm.engine.RuntimeService;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import static de.weinbrecht.luc.bpm.architecture.recommendation.adapter.common.ProcessConstants.CUSTOMER_NUMBER;
|
||||
import static de.weinbrecht.luc.bpm.architecture.recommendation.adapter.common.ProcessConstants.START_EVENT_MESSAGE_REF;
|
||||
import static java.util.Collections.singletonMap;
|
||||
|
||||
@Slf4j
|
||||
@RequiredArgsConstructor
|
||||
@Component
|
||||
public class ProcessEngineClient implements StartRecommendation {
|
||||
|
||||
private final RuntimeService runtimeService;
|
||||
private final ZeebeClient client;
|
||||
|
||||
@Override
|
||||
public void start(String caseId, CustomerId customerId) {
|
||||
runtimeService.startProcessInstanceByMessage(
|
||||
START_EVENT_MESSAGE_REF,
|
||||
caseId,
|
||||
singletonMap(CUSTOMER_NUMBER, customerId.getValue())
|
||||
);
|
||||
log.info("Starting new recommendation for loan agreement with case ID {}", caseId);
|
||||
client.newPublishMessageCommand()
|
||||
.messageName(START_EVENT_MESSAGE_REF)
|
||||
.correlationKey("")
|
||||
.variables(singletonMap(CUSTOMER_NUMBER, customerId.getValue()))
|
||||
.send()
|
||||
.exceptionally(throwable -> {
|
||||
throw new CouldNotPublishMessageException(throwable);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,12 +5,12 @@ spring:
|
||||
hibernate:
|
||||
ddl-auto: create-drop
|
||||
|
||||
camunda.bpm.admin-user:
|
||||
id: admin
|
||||
password: pw
|
||||
generic-properties:
|
||||
properties:
|
||||
initializeTelemetry: false
|
||||
zeebe:
|
||||
client:
|
||||
cloud:
|
||||
cluster-id: <your cluster ID>
|
||||
client-id: <your client ID>
|
||||
client-secret: <you secret>
|
||||
|
||||
server:
|
||||
port: 8081
|
||||
@@ -1,65 +1,71 @@
|
||||
<?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:dc="http://www.omg.org/spec/DD/20100524/DC" xmlns:camunda="http://camunda.org/schema/1.0/bpmn" xmlns:di="http://www.omg.org/spec/DD/20100524/DI" xmlns:modeler="http://camunda.org/schema/modeler/1.0" id="Definitions_02tppq7" targetNamespace="http://bpmn.io/schema/bpmn" exporter="Camunda Modeler" exporterVersion="5.0.0" modeler:executionPlatform="Camunda Platform" modeler:executionPlatformVersion="7.17.0">
|
||||
<bpmn:collaboration id="Collaboration_197td5k">
|
||||
<bpmn:participant id="Participant_1y5ozpf" name="Recommendation example" processRef="Cross_Selling_Recommendation" />
|
||||
<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:di="http://www.omg.org/spec/DD/20100524/DI" xmlns:modeler="http://camunda.org/schema/modeler/1.0" id="Definitions_19vu2ot" targetNamespace="http://bpmn.io/schema/bpmn" exporter="Camunda Modeler" exporterVersion="5.0.0" modeler:executionPlatform="Camunda Cloud" modeler:executionPlatformVersion="8.0.0">
|
||||
<bpmn:collaboration id="Collaboration_10bbmwl">
|
||||
<bpmn:participant id="Participant_04vjgyb" name="Recommendation example" processRef="Cross_Selling_Recommendation" />
|
||||
</bpmn:collaboration>
|
||||
<bpmn:process id="Cross_Selling_Recommendation" name="Cross Selling Recommendation" isExecutable="true">
|
||||
<bpmn:serviceTask id="PickContentServiceTask" name="Pick Content" camunda:delegateExpression="${pickContent}">
|
||||
<bpmn:incoming>Flow_14xjzhy</bpmn:incoming>
|
||||
<bpmn:outgoing>Flow_0klv1t5</bpmn:outgoing>
|
||||
</bpmn:serviceTask>
|
||||
<bpmn:startEvent id="CrossSellingStartEvent" name="Cross-Selling potential discovered">
|
||||
<bpmn:outgoing>Flow_14xjzhy</bpmn:outgoing>
|
||||
<bpmn:messageEventDefinition id="MessageEventDefinition_1ibjd8g" messageRef="Message_0hftsl2" />
|
||||
<bpmn:outgoing>Flow_0mgasl2</bpmn:outgoing>
|
||||
<bpmn:messageEventDefinition id="MessageEventDefinition_0mygnsf" messageRef="Message_2r0s72o" />
|
||||
</bpmn:startEvent>
|
||||
<bpmn:sequenceFlow id="Flow_14xjzhy" sourceRef="CrossSellingStartEvent" targetRef="PickContentServiceTask" />
|
||||
<bpmn:endEvent id="CrossSellingRecommendationEndEvent" name="Cross-Selling recommendation sent">
|
||||
<bpmn:incoming>Flow_1bnyw18</bpmn:incoming>
|
||||
<bpmn:endEvent id="CrossSellingRecommendationEndEvent" name="Cross-Selling recommendation sent">
|
||||
<bpmn:incoming>Flow_12yp5p7</bpmn:incoming>
|
||||
</bpmn:endEvent>
|
||||
<bpmn:sequenceFlow id="Flow_0klv1t5" sourceRef="PickContentServiceTask" targetRef="SendRecommendationServiceTask" />
|
||||
<bpmn:sequenceFlow id="Flow_1bnyw18" sourceRef="SendRecommendationServiceTask" targetRef="CrossSellingRecommendationEndEvent" />
|
||||
<bpmn:serviceTask id="SendRecommendationServiceTask" name="Send Recommendation" camunda:delegateExpression="${sendRecommendation}">
|
||||
<bpmn:incoming>Flow_0klv1t5</bpmn:incoming>
|
||||
<bpmn:outgoing>Flow_1bnyw18</bpmn:outgoing>
|
||||
<bpmn:serviceTask id="SendRecommendationServiceTask" name="Send Recommendation">
|
||||
<bpmn:extensionElements>
|
||||
<zeebe:taskDefinition type="send-recommendation" />
|
||||
</bpmn:extensionElements>
|
||||
<bpmn:incoming>Flow_1lskvls</bpmn:incoming>
|
||||
<bpmn:outgoing>Flow_12yp5p7</bpmn:outgoing>
|
||||
</bpmn:serviceTask>
|
||||
<bpmn:serviceTask id="PickContentServiceTask" name="Pick Content">
|
||||
<bpmn:extensionElements>
|
||||
<zeebe:taskDefinition type="pick-content" />
|
||||
</bpmn:extensionElements>
|
||||
<bpmn:incoming>Flow_0mgasl2</bpmn:incoming>
|
||||
<bpmn:outgoing>Flow_1lskvls</bpmn:outgoing>
|
||||
</bpmn:serviceTask>
|
||||
<bpmn:sequenceFlow id="Flow_0mgasl2" sourceRef="CrossSellingStartEvent" targetRef="PickContentServiceTask" />
|
||||
<bpmn:sequenceFlow id="Flow_1lskvls" sourceRef="PickContentServiceTask" targetRef="SendRecommendationServiceTask" />
|
||||
<bpmn:sequenceFlow id="Flow_12yp5p7" sourceRef="SendRecommendationServiceTask" targetRef="CrossSellingRecommendationEndEvent" />
|
||||
</bpmn:process>
|
||||
<bpmn:message id="Message_0hftsl2" name="crossSellingPotentialDiscoveredMessage" />
|
||||
<bpmn:message id="Message_2r0s72o" name="crossSellingPotentialDiscoveredMessage" />
|
||||
<bpmndi:BPMNDiagram id="BPMNDiagram_1">
|
||||
<bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="Collaboration_197td5k">
|
||||
<bpmndi:BPMNShape id="Participant_1y5ozpf_di" bpmnElement="Participant_1y5ozpf" isHorizontal="true">
|
||||
<dc:Bounds x="160" y="80" width="610" height="180" />
|
||||
<bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="Collaboration_10bbmwl">
|
||||
<bpmndi:BPMNShape id="Participant_04vjgyb_di" bpmnElement="Participant_04vjgyb" isHorizontal="true">
|
||||
<dc:Bounds x="160" y="90" width="600" height="190" />
|
||||
<bpmndi:BPMNLabel />
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNEdge id="Flow_1bnyw18_di" bpmnElement="Flow_1bnyw18">
|
||||
<di:waypoint x="600" y="160" />
|
||||
<di:waypoint x="662" y="160" />
|
||||
<bpmndi:BPMNEdge id="Flow_12yp5p7_di" bpmnElement="Flow_12yp5p7">
|
||||
<di:waypoint x="600" y="177" />
|
||||
<di:waypoint x="662" y="177" />
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNEdge id="Flow_0klv1t5_di" bpmnElement="Flow_0klv1t5">
|
||||
<di:waypoint x="440" y="160" />
|
||||
<di:waypoint x="500" y="160" />
|
||||
<bpmndi:BPMNEdge id="Flow_1lskvls_di" bpmnElement="Flow_1lskvls">
|
||||
<di:waypoint x="440" y="177" />
|
||||
<di:waypoint x="500" y="177" />
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNEdge id="Flow_14xjzhy_di" bpmnElement="Flow_14xjzhy">
|
||||
<di:waypoint x="288" y="160" />
|
||||
<di:waypoint x="340" y="160" />
|
||||
<bpmndi:BPMNEdge id="Flow_0mgasl2_di" bpmnElement="Flow_0mgasl2">
|
||||
<di:waypoint x="285" y="177" />
|
||||
<di:waypoint x="340" y="177" />
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNShape id="Activity_0bsm7s4_di" bpmnElement="PickContentServiceTask">
|
||||
<dc:Bounds x="340" y="120" width="100" height="80" />
|
||||
<bpmndi:BPMNShape id="Event_076x431_di" bpmnElement="CrossSellingStartEvent">
|
||||
<dc:Bounds x="249" y="159" width="36" height="36" />
|
||||
<bpmndi:BPMNLabel>
|
||||
<dc:Bounds x="234" y="202" width="66" height="40" />
|
||||
</bpmndi:BPMNLabel>
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape id="Event_0hl5l0c_di" bpmnElement="CrossSellingRecommendationEndEvent">
|
||||
<dc:Bounds x="662" y="159" width="36" height="36" />
|
||||
<bpmndi:BPMNLabel>
|
||||
<dc:Bounds x="639" y="202" width="82" height="40" />
|
||||
</bpmndi:BPMNLabel>
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape id="Activity_1aukzy6_di" bpmnElement="SendRecommendationServiceTask">
|
||||
<dc:Bounds x="500" y="137" width="100" height="80" />
|
||||
<bpmndi:BPMNLabel />
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape id="Event_1ct00fd_di" bpmnElement="CrossSellingStartEvent">
|
||||
<dc:Bounds x="252" y="142" width="36" height="36" />
|
||||
<bpmndi:BPMNLabel>
|
||||
<dc:Bounds x="237" y="185" width="66" height="40" />
|
||||
</bpmndi:BPMNLabel>
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape id="Event_18vk8pm_di" bpmnElement="CrossSellingRecommendationEndEvent">
|
||||
<dc:Bounds x="662" y="142" width="36" height="36" />
|
||||
<bpmndi:BPMNLabel>
|
||||
<dc:Bounds x="640" y="185" width="82" height="40" />
|
||||
</bpmndi:BPMNLabel>
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape id="BPMNShape_1shn36r" bpmnElement="SendRecommendationServiceTask">
|
||||
<dc:Bounds x="500" y="120" width="100" height="80" />
|
||||
<bpmndi:BPMNShape id="Activity_0eljoe1_di" bpmnElement="PickContentServiceTask">
|
||||
<dc:Bounds x="340" y="137" width="100" height="80" />
|
||||
<bpmndi:BPMNLabel />
|
||||
</bpmndi:BPMNShape>
|
||||
</bpmndi:BPMNPlane>
|
||||
|
||||
@@ -1,52 +1,46 @@
|
||||
package de.weinbrecht.luc.bpm.architecture.recommendation;
|
||||
|
||||
import de.weinbrecht.luc.bpm.architecture.recommendation.adapter.in.process.PickContent;
|
||||
import de.weinbrecht.luc.bpm.architecture.recommendation.adapter.in.process.SendRecommendation;
|
||||
import org.camunda.bpm.engine.runtime.ProcessInstance;
|
||||
import org.camunda.bpm.engine.test.Deployment;
|
||||
import org.camunda.bpm.extension.junit5.test.ProcessEngineExtension;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import io.camunda.zeebe.client.ZeebeClient;
|
||||
import io.camunda.zeebe.client.api.response.PublishMessageResponse;
|
||||
import io.camunda.zeebe.process.test.api.ZeebeTestEngine;
|
||||
import io.camunda.zeebe.process.test.extension.ZeebeProcessTest;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
|
||||
import static org.camunda.bpm.engine.test.assertions.bpmn.BpmnAwareTests.assertThat;
|
||||
import static org.camunda.bpm.engine.test.assertions.bpmn.BpmnAwareTests.runtimeService;
|
||||
import static org.camunda.community.mockito.DelegateExpressions.registerJavaDelegateMock;
|
||||
import static org.camunda.community.mockito.DelegateExpressions.verifyJavaDelegateMock;
|
||||
import static de.weinbrecht.luc.bpm.architecture.ProcessTestUtils.*;
|
||||
import static de.weinbrecht.luc.bpm.architecture.recommendation.adapter.common.ProcessConstants.*;
|
||||
import static io.camunda.zeebe.process.test.assertions.BpmnAssert.assertThat;
|
||||
|
||||
@ExtendWith(ProcessEngineExtension.class)
|
||||
@ZeebeProcessTest
|
||||
class ProcessTest {
|
||||
|
||||
public static final String PROCESS_DEFINITION = "Cross_Selling_Recommendation";
|
||||
private ZeebeTestEngine engine;
|
||||
private ZeebeClient client;
|
||||
|
||||
private static final String START_EVENT = "CrossSellingStartEvent";
|
||||
private static final String PICK_CONTENT_SERVICE_TASK = "PickContentServiceTask";
|
||||
private static final String SEND_RECOMMENDATION_SERVICE_TASK = "SendRecommendationServiceTask";
|
||||
private static final String END_EVENT = "CrossSellingRecommendationEndEvent";
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
registerJavaDelegateMock(PickContent.class);
|
||||
registerJavaDelegateMock(SendRecommendation.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
@Deployment(resources = "cross_selling_recommendation.bpmn")
|
||||
void shouldExecuteProcess_happy_path() {
|
||||
ProcessInstance processInstance = runtimeService().startProcessInstanceByKey(
|
||||
PROCESS_DEFINITION
|
||||
);
|
||||
void testRunsProcess() throws Exception {
|
||||
deployResource(client, "cross_selling_recommendation.bpmn");
|
||||
|
||||
assertThat(processInstance)
|
||||
.hasPassedInOrder(
|
||||
START_EVENT,
|
||||
PICK_CONTENT_SERVICE_TASK,
|
||||
SEND_RECOMMENDATION_SERVICE_TASK,
|
||||
END_EVENT);
|
||||
final PublishMessageResponse response = sendMessage(engine, client, START_EVENT_MESSAGE_REF, "");
|
||||
|
||||
verifyJavaDelegateMock(PickContent.class).executed();
|
||||
verifyJavaDelegateMock(SendRecommendation.class).executed();
|
||||
assertThat(response).extractingProcessInstance()
|
||||
.hasPassedElement(START_EVENT)
|
||||
.isWaitingAtElements(PICK_CONTENT_SERVICE_TASK);
|
||||
|
||||
assertThat(processInstance).isEnded();
|
||||
completeTaskWithType(engine , client, PICK_CONTENT_SERVICE_TASK, PICK_CONTENT_TASK);
|
||||
|
||||
assertThat(response).extractingProcessInstance()
|
||||
.hasPassedElement(PICK_CONTENT_SERVICE_TASK)
|
||||
.isWaitingAtElements(SEND_RECOMMENDATION_SERVICE_TASK);
|
||||
|
||||
completeTaskWithType(engine , client, SEND_RECOMMENDATION_SERVICE_TASK, SEND_RECOMMENDATION_TASK);
|
||||
|
||||
assertThat(response).extractingProcessInstance()
|
||||
.hasPassedElementsInOrder(SEND_RECOMMENDATION_SERVICE_TASK, END_EVENT)
|
||||
.isCompleted();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,99 @@
|
||||
package de.weinbrecht.luc.bpm.architecture.recommendation;
|
||||
|
||||
import de.weinbrecht.luc.bpm.architecture.recommendation.domain.model.Content;
|
||||
import de.weinbrecht.luc.bpm.architecture.recommendation.domain.model.ContentId;
|
||||
import de.weinbrecht.luc.bpm.architecture.recommendation.domain.model.Description;
|
||||
import de.weinbrecht.luc.bpm.architecture.recommendation.domain.model.Recommendation;
|
||||
import de.weinbrecht.luc.bpm.architecture.recommendation.domain.model.customer.Customer;
|
||||
import de.weinbrecht.luc.bpm.architecture.recommendation.domain.model.customer.CustomerId;
|
||||
import de.weinbrecht.luc.bpm.architecture.recommendation.domain.model.customer.MailAddress;
|
||||
import de.weinbrecht.luc.bpm.architecture.recommendation.domain.model.customer.Name;
|
||||
import de.weinbrecht.luc.bpm.architecture.recommendation.usecase.in.RecommendationCreation;
|
||||
import de.weinbrecht.luc.bpm.architecture.recommendation.usecase.in.RecommendationPicker;
|
||||
import de.weinbrecht.luc.bpm.architecture.recommendation.usecase.out.RecommendationQuery;
|
||||
import de.weinbrecht.luc.bpm.architecture.recommendation.usecase.out.SendNotification;
|
||||
import io.camunda.zeebe.client.ZeebeClient;
|
||||
import io.camunda.zeebe.process.test.api.ZeebeTestEngine;
|
||||
import io.camunda.zeebe.spring.test.ZeebeSpringTest;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Disabled;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.boot.test.mock.mockito.MockBean;
|
||||
import org.springframework.test.annotation.DirtiesContext;
|
||||
|
||||
import static de.weinbrecht.luc.bpm.architecture.SpringProcessTestUtils.isProcessInstanceCompleted;
|
||||
import static de.weinbrecht.luc.bpm.architecture.SpringProcessTestUtils.waitForTaskAndComplete;
|
||||
import static de.weinbrecht.luc.bpm.architecture.recommendation.adapter.common.ProcessConstants.*;
|
||||
import static io.camunda.zeebe.process.test.filters.RecordStream.of;
|
||||
import static java.util.Collections.singletonMap;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
import static org.springframework.test.annotation.DirtiesContext.ClassMode.BEFORE_CLASS;
|
||||
|
||||
// https://github.com/camunda-community-hub/camunda-8-examples/blob/main/twitter-review-java-springboot/src/test/java/org/camunda/community/examples/twitter/TestTwitterProcess.java
|
||||
@SpringBootTest
|
||||
@ZeebeSpringTest
|
||||
@Disabled("Flaky if runs together with the non Spring Process Tests")
|
||||
@MockBean(RecommendationCreation.class)
|
||||
class SpringProcessTest {
|
||||
|
||||
@Autowired
|
||||
private ZeebeClient zeebe;
|
||||
|
||||
@Autowired
|
||||
private ZeebeTestEngine zeebeTestEngine;
|
||||
|
||||
@MockBean
|
||||
private RecommendationPicker recommendationPicker;
|
||||
|
||||
@MockBean
|
||||
private RecommendationQuery recommendationQuery;
|
||||
|
||||
@MockBean
|
||||
private SendNotification sendNotification;
|
||||
|
||||
private final ContentId contentId = new ContentId(1L);
|
||||
private final CustomerId customerId = new CustomerId("Test-11");
|
||||
private final Recommendation recommendation = new Recommendation(
|
||||
new Customer(
|
||||
customerId,
|
||||
new Name("Tester"),
|
||||
new MailAddress("tester@ewb.io")
|
||||
),
|
||||
new Content(contentId, new Description("Foo"))
|
||||
);
|
||||
|
||||
public static final String PROCESS_DEFINITION = "Cross_Selling_Recommendation";
|
||||
|
||||
private static final String PICK_CONTENT_SERVICE_TASK = "PickContentServiceTask";
|
||||
private static final String SEND_RECOMMENDATION_SERVICE_TASK = "SendRecommendationServiceTask";
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
when(recommendationPicker.pickContent()).thenReturn(contentId);
|
||||
|
||||
when(recommendationQuery.findContentById(contentId)).thenReturn(recommendation.getContent());
|
||||
when(recommendationQuery.findCustomerById(customerId)).thenReturn(recommendation.getCustomer());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testRunsProcess() throws Exception {
|
||||
zeebe.newPublishMessageCommand().messageName(START_EVENT_MESSAGE_REF)
|
||||
.correlationKey("")
|
||||
.variables(singletonMap(CUSTOMER_NUMBER, customerId.getValue()))
|
||||
.send();
|
||||
|
||||
waitForTaskAndComplete(zeebeTestEngine, zeebe, PICK_CONTENT_SERVICE_TASK, PICK_CONTENT_TASK, singletonMap(CONTENT_NUMBER, contentId.getValue()));
|
||||
|
||||
verify(recommendationPicker).pickContent();
|
||||
|
||||
waitForTaskAndComplete(zeebeTestEngine, zeebe, SEND_RECOMMENDATION_SERVICE_TASK, SEND_RECOMMENDATION_TASK);
|
||||
|
||||
verify(sendNotification).send(recommendation);
|
||||
|
||||
assertTrue(isProcessInstanceCompleted(of(zeebeTestEngine.getRecordStreamSource()), PROCESS_DEFINITION));
|
||||
}
|
||||
}
|
||||
@@ -2,13 +2,18 @@ package de.weinbrecht.luc.bpm.architecture.recommendation.adapter.in.process;
|
||||
|
||||
import de.weinbrecht.luc.bpm.architecture.recommendation.domain.model.ContentId;
|
||||
import de.weinbrecht.luc.bpm.architecture.recommendation.usecase.in.RecommendationPicker;
|
||||
import org.camunda.bpm.engine.delegate.DelegateExecution;
|
||||
import io.camunda.zeebe.client.api.response.ActivatedJob;
|
||||
import io.camunda.zeebe.client.api.worker.JobClient;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoSettings;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import static de.weinbrecht.luc.bpm.architecture.recommendation.adapter.common.ProcessConstants.CONTENT_NUMBER;
|
||||
import static org.mockito.Answers.RETURNS_DEEP_STUBS;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
@MockitoSettings
|
||||
@@ -24,10 +29,14 @@ class PickContentTest {
|
||||
void should_call_service_and_set_variable() {
|
||||
ContentId contentId = new ContentId(1L);
|
||||
when(recommendationPicker.pickContent()).thenReturn(contentId);
|
||||
DelegateExecution delegateExecution = mock(DelegateExecution.class);
|
||||
JobClient client = mock(JobClient.class, RETURNS_DEEP_STUBS);
|
||||
ActivatedJob job = mock(ActivatedJob.class);
|
||||
when(job.getKey()).thenReturn(1L);
|
||||
Map<String, Object> variables = new HashMap<>();
|
||||
variables.put(CONTENT_NUMBER, contentId.getValue());
|
||||
|
||||
classUnderTest.execute(delegateExecution);
|
||||
classUnderTest.handleJobFoo(client, job);
|
||||
|
||||
verify(delegateExecution).setVariable(CONTENT_NUMBER, contentId.getValue());
|
||||
verify(client.newCompleteCommand(job.getKey()).variables(variables)).send();
|
||||
}
|
||||
}
|
||||
@@ -10,14 +10,19 @@ import de.weinbrecht.luc.bpm.architecture.recommendation.domain.model.customer.M
|
||||
import de.weinbrecht.luc.bpm.architecture.recommendation.domain.model.customer.Name;
|
||||
import de.weinbrecht.luc.bpm.architecture.recommendation.usecase.out.RecommendationQuery;
|
||||
import de.weinbrecht.luc.bpm.architecture.recommendation.usecase.out.SendNotification;
|
||||
import org.camunda.bpm.engine.delegate.DelegateExecution;
|
||||
import io.camunda.zeebe.client.api.response.ActivatedJob;
|
||||
import io.camunda.zeebe.client.api.worker.JobClient;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoSettings;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import static de.weinbrecht.luc.bpm.architecture.recommendation.adapter.common.ProcessConstants.CONTENT_NUMBER;
|
||||
import static de.weinbrecht.luc.bpm.architecture.recommendation.adapter.common.ProcessConstants.CUSTOMER_NUMBER;
|
||||
import static org.mockito.Answers.RETURNS_DEEP_STUBS;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
@MockitoSettings
|
||||
@@ -35,16 +40,13 @@ class SendRecommendationTest {
|
||||
@Test
|
||||
void should_load_data_and_call_service_to_send_notification() {
|
||||
ContentId contentId = new ContentId(1L);
|
||||
CustomerId customerId = new CustomerId("A1");
|
||||
DelegateExecution delegateExecution = mock(DelegateExecution.class);
|
||||
when(delegateExecution.getVariable(CONTENT_NUMBER)).thenReturn(contentId.getValue());
|
||||
when(delegateExecution.getVariable(CUSTOMER_NUMBER)).thenReturn(customerId.getValue());
|
||||
CustomerId customerNumber = new CustomerId("A1");
|
||||
Content content = new Content(contentId, new Description("Foo"));
|
||||
when(recommendationQuery.findContentById(contentId)).thenReturn(content);
|
||||
Customer customer = createCustomer(customerId);
|
||||
when(recommendationQuery.findCustomerById(customerId)).thenReturn(customer);
|
||||
Customer customer = createCustomer(customerNumber);
|
||||
when(recommendationQuery.findCustomerById(customerNumber)).thenReturn(customer);
|
||||
|
||||
classUnderTest.execute(delegateExecution);
|
||||
classUnderTest.handleJobFoo(contentId.getValue(), customerNumber.getValue());
|
||||
|
||||
verify(sendNotification).send(new Recommendation(customer, content));
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package de.weinbrecht.luc.bpm.architecture.recommendation.adapter.out.process;
|
||||
|
||||
import de.weinbrecht.luc.bpm.architecture.recommendation.domain.model.customer.CustomerId;
|
||||
import org.camunda.bpm.engine.RuntimeService;
|
||||
import io.camunda.zeebe.client.ZeebeClient;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
@@ -10,6 +10,7 @@ import org.mockito.junit.jupiter.MockitoSettings;
|
||||
import static de.weinbrecht.luc.bpm.architecture.recommendation.adapter.common.ProcessConstants.CUSTOMER_NUMBER;
|
||||
import static de.weinbrecht.luc.bpm.architecture.recommendation.adapter.common.ProcessConstants.START_EVENT_MESSAGE_REF;
|
||||
import static java.util.Collections.singletonMap;
|
||||
import static org.mockito.Answers.RETURNS_DEEP_STUBS;
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
||||
@MockitoSettings
|
||||
@@ -18,8 +19,8 @@ class ProcessEngineClientTest {
|
||||
@InjectMocks
|
||||
private ProcessEngineClient classUnderTest;
|
||||
|
||||
@Mock
|
||||
private RuntimeService runtimeService;
|
||||
@Mock(answer = RETURNS_DEEP_STUBS)
|
||||
private ZeebeClient zeebeClient;
|
||||
|
||||
@Test
|
||||
void should_class_runtime_service_to_start() {
|
||||
@@ -28,10 +29,11 @@ class ProcessEngineClientTest {
|
||||
|
||||
classUnderTest.start(caseId, customerId);
|
||||
|
||||
verify(runtimeService).startProcessInstanceByMessage(
|
||||
START_EVENT_MESSAGE_REF,
|
||||
caseId,
|
||||
singletonMap(CUSTOMER_NUMBER, customerId.getValue())
|
||||
);
|
||||
verify(zeebeClient
|
||||
.newPublishMessageCommand()
|
||||
.messageName(START_EVENT_MESSAGE_REF)
|
||||
.correlationKey("")
|
||||
.variables(singletonMap(CUSTOMER_NUMBER, customerId.getValue()))
|
||||
).send();
|
||||
}
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
spring.datasource.url: jdbc:h2:file:./camunda-h2-database
|
||||
|
||||
camunda.bpm.admin-user:
|
||||
id: admin
|
||||
password: pw
|
||||
@@ -1,22 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<beans xmlns="http://www.springframework.org/schema/beans"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
|
||||
|
||||
<bean id="processEngineConfiguration" class="org.camunda.bpm.engine.impl.cfg.StandaloneInMemProcessEngineConfiguration">
|
||||
|
||||
<property name="jdbcUrl" value="jdbc:h2:mem:camunda;DB_CLOSE_DELAY=1000" />
|
||||
<property name="jdbcDriver" value="org.h2.Driver" />
|
||||
<property name="jdbcUsername" value="sa" />
|
||||
<property name="jdbcPassword" value="" />
|
||||
|
||||
<!-- Database configurations -->
|
||||
<property name="databaseSchemaUpdate" value="true" />
|
||||
|
||||
<!-- job executor configurations -->
|
||||
<property name="jobExecutorActivate" value="false" />
|
||||
|
||||
<property name="history" value="full" />
|
||||
</bean>
|
||||
</beans>
|
||||
@@ -14,9 +14,9 @@
|
||||
|
||||
<version.junit5>5.8.2</version.junit5>
|
||||
<version.bpmAssert>1.1.0</version.bpmAssert>
|
||||
<version.camundaMockito>6.17.0</version.camundaMockito>
|
||||
<version.archunitJunit5>0.23.1</version.archunitJunit5>
|
||||
<version.surefirePlugin>3.0.0-M6</version.surefirePlugin>
|
||||
<version.zeebe>8.0.1</version.zeebe>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
@@ -38,17 +38,23 @@
|
||||
<version>${version.bpmAssert}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.camunda.community.mockito</groupId>
|
||||
<artifactId>camunda-platform-7-mockito</artifactId>
|
||||
<version>${version.camundaMockito}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.tngtech.archunit</groupId>
|
||||
<artifactId>archunit-junit5</artifactId>
|
||||
<version>${version.archunitJunit5}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.camunda</groupId>
|
||||
<artifactId>spring-zeebe-test</artifactId>
|
||||
<version>${version.zeebe}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.camunda</groupId>
|
||||
<artifactId>zeebe-process-test-extension</artifactId>
|
||||
<version>${version.zeebe}</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
|
||||
@@ -0,0 +1,110 @@
|
||||
package de.weinbrecht.luc.bpm.architecture;
|
||||
|
||||
import io.camunda.zeebe.client.ZeebeClient;
|
||||
import io.camunda.zeebe.client.api.command.DeployResourceCommandStep1;
|
||||
import io.camunda.zeebe.client.api.response.ActivateJobsResponse;
|
||||
import io.camunda.zeebe.client.api.response.DeploymentEvent;
|
||||
import io.camunda.zeebe.client.api.response.PublishMessageResponse;
|
||||
import io.camunda.zeebe.process.test.api.ZeebeTestEngine;
|
||||
import io.camunda.zeebe.process.test.filters.RecordStream;
|
||||
import io.camunda.zeebe.protocol.record.Record;
|
||||
import io.camunda.zeebe.protocol.record.value.JobRecordValue;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
|
||||
import static io.camunda.zeebe.process.test.filters.StreamFilter.jobRecords;
|
||||
import static io.camunda.zeebe.protocol.record.intent.JobIntent.COMPLETED;
|
||||
import static io.camunda.zeebe.protocol.record.intent.JobIntent.CREATED;
|
||||
import static java.lang.String.format;
|
||||
import static java.time.Duration.ofSeconds;
|
||||
import static java.util.Collections.emptyMap;
|
||||
import static java.util.stream.Collectors.toList;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
// https://github.com/camunda/zeebe-process-test/blob/main/qa/abstracts/src/main/java/io/camunda/zeebe/process/test/qa/abstracts/util/Utilities.java
|
||||
public class ProcessTestUtils {
|
||||
|
||||
public static DeploymentEvent deployResource(final ZeebeClient client, final String resource) {
|
||||
return deployResources(client, resource);
|
||||
}
|
||||
|
||||
public static DeploymentEvent deployResources(
|
||||
final ZeebeClient client, final String... resources) {
|
||||
final DeployResourceCommandStep1 commandStep1 = client.newDeployResourceCommand();
|
||||
|
||||
DeployResourceCommandStep1.DeployResourceCommandStep2 commandStep2 = null;
|
||||
for (final String process : resources) {
|
||||
if (commandStep2 == null) {
|
||||
commandStep2 = commandStep1.addResourceFromClasspath(process);
|
||||
} else {
|
||||
commandStep2 = commandStep2.addResourceFromClasspath(process);
|
||||
}
|
||||
}
|
||||
|
||||
return commandStep2.send().join();
|
||||
}
|
||||
|
||||
public static PublishMessageResponse sendMessage(
|
||||
final ZeebeTestEngine engine,
|
||||
final ZeebeClient client,
|
||||
final String messageName,
|
||||
final String correlationKey)
|
||||
throws InterruptedException, TimeoutException {
|
||||
return sendMessage(engine, client, messageName, correlationKey, emptyMap());
|
||||
}
|
||||
|
||||
public static PublishMessageResponse sendMessage(
|
||||
final ZeebeTestEngine engine,
|
||||
final ZeebeClient client,
|
||||
final String messageName,
|
||||
final String correlationKey,
|
||||
final Map<String, Object> variables)
|
||||
throws InterruptedException, TimeoutException {
|
||||
final PublishMessageResponse response =
|
||||
client
|
||||
.newPublishMessageCommand()
|
||||
.messageName(messageName)
|
||||
.correlationKey(correlationKey)
|
||||
.variables(variables)
|
||||
.send()
|
||||
.join();
|
||||
engine.waitForIdleState(ofSeconds(1));
|
||||
return response;
|
||||
}
|
||||
|
||||
public static ActivateJobsResponse activateSingleJob(
|
||||
final ZeebeClient client, final String jobType) {
|
||||
return client.newActivateJobsCommand().jobType(jobType).maxJobsToActivate(1).send().join();
|
||||
}
|
||||
|
||||
public static void completeTaskWithType(
|
||||
final ZeebeTestEngine engine, final ZeebeClient client, final String taskId, final String jobType)
|
||||
throws InterruptedException, TimeoutException {
|
||||
final List<Record<JobRecordValue>> records =
|
||||
jobRecords(RecordStream.of(engine.getRecordStreamSource()))
|
||||
.withElementId(taskId)
|
||||
.withIntent(CREATED)
|
||||
.stream()
|
||||
.collect(toList());
|
||||
|
||||
jobRecords(RecordStream.of(engine.getRecordStreamSource()))
|
||||
.withElementId(taskId)
|
||||
.withIntent(COMPLETED)
|
||||
.stream()
|
||||
.forEach(record -> records.removeIf(r -> record.getKey() == r.getKey()));
|
||||
|
||||
if (!records.isEmpty()) {
|
||||
final Record<JobRecordValue> lastRecord;
|
||||
lastRecord = records.get(records.size() - 1);
|
||||
assertThat(lastRecord.getValue().getType()).isEqualTo(jobType);
|
||||
client.newCompleteCommand(lastRecord.getKey()).send().join();
|
||||
} else {
|
||||
throw new IllegalStateException(
|
||||
format("Tried to complete task `%s`, but it was not found", taskId));
|
||||
}
|
||||
|
||||
engine.waitForIdleState(ofSeconds(1));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
package de.weinbrecht.luc.bpm.architecture;
|
||||
|
||||
import io.camunda.zeebe.client.ZeebeClient;
|
||||
import io.camunda.zeebe.client.api.response.ActivatedJob;
|
||||
import io.camunda.zeebe.process.test.api.ZeebeTestEngine;
|
||||
import io.camunda.zeebe.process.test.filters.RecordStream;
|
||||
import io.camunda.zeebe.protocol.record.Record;
|
||||
import io.camunda.zeebe.protocol.record.value.ProcessInstanceRecordValue;
|
||||
import org.opentest4j.AssertionFailedError;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
|
||||
import static io.camunda.zeebe.process.test.filters.StreamFilter.processInstance;
|
||||
import static io.camunda.zeebe.protocol.record.RejectionType.NULL_VAL;
|
||||
import static io.camunda.zeebe.protocol.record.intent.ProcessInstanceIntent.ELEMENT_COMPLETED;
|
||||
import static io.camunda.zeebe.protocol.record.value.BpmnElementType.PROCESS;
|
||||
import static java.time.Duration.ofSeconds;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
public class SpringProcessTestUtils {
|
||||
|
||||
public static ActivatedJob waitForTaskAndComplete(
|
||||
ZeebeTestEngine zeebeTestEngine,
|
||||
ZeebeClient zeebe,
|
||||
String taskId,
|
||||
String jobType) throws Exception {
|
||||
int maxRetry = 5;
|
||||
|
||||
ActivatedJob taskJob = null;
|
||||
for (int i = 0; i < maxRetry; i++) {
|
||||
taskJob = waitAndFetchJobs(zeebeTestEngine, zeebe, jobType);
|
||||
if (taskJob != null) {
|
||||
// Make sure it is the right one
|
||||
assertEquals(taskId, taskJob.getElementId());
|
||||
zeebe.newCompleteCommand(taskJob.getKey()).send().join();
|
||||
}
|
||||
}
|
||||
return taskJob;
|
||||
}
|
||||
|
||||
public static ActivatedJob waitForTaskAndComplete(
|
||||
ZeebeTestEngine zeebeTestEngine,
|
||||
ZeebeClient zeebe,
|
||||
String taskId,
|
||||
String jobType,
|
||||
Map<String, Object> variables) throws Exception {
|
||||
int maxRetry = 5;
|
||||
|
||||
ActivatedJob taskJob = null;
|
||||
for (int i = 0; i < maxRetry; i++) {
|
||||
taskJob = waitAndFetchJobs(zeebeTestEngine, zeebe, jobType);
|
||||
if (taskJob != null) {
|
||||
// Make sure it is the right one
|
||||
assertEquals(taskId, taskJob.getElementId());
|
||||
zeebe.newCompleteCommand(taskJob.getKey()).variables(variables).send().join();
|
||||
}
|
||||
}
|
||||
return taskJob;
|
||||
}
|
||||
|
||||
public static ActivatedJob waitAndFetchJobs(
|
||||
ZeebeTestEngine zeebeTestEngine,
|
||||
ZeebeClient zeebe,
|
||||
String jobType) throws InterruptedException, TimeoutException {
|
||||
// Let the workflow engine do whatever it needs to do
|
||||
zeebeTestEngine.waitForIdleState(ofSeconds(5));
|
||||
|
||||
// Now get all user tasks
|
||||
List<ActivatedJob> jobs = zeebe.newActivateJobsCommand().jobType(jobType).maxJobsToActivate(1).send().join().getJobs();
|
||||
|
||||
if (jobs.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
return jobs.get(0);
|
||||
}
|
||||
|
||||
public static boolean isProcessInstanceCompleted(RecordStream recordStream, String bpmnProcessId) {
|
||||
return processInstance(recordStream).withBpmnProcessId(bpmnProcessId)
|
||||
.withRejectionType(NULL_VAL)
|
||||
.withBpmnElementType(PROCESS)
|
||||
.withIntent(ELEMENT_COMPLETED)
|
||||
.stream().findFirst().isPresent();
|
||||
}
|
||||
|
||||
public static Record<ProcessInstanceRecordValue> getProcessInstance(RecordStream recordStream, String bpmnProcessId) {
|
||||
return processInstance(recordStream).withBpmnProcessId(bpmnProcessId)
|
||||
.withRejectionType(NULL_VAL)
|
||||
.withBpmnElementType(PROCESS)
|
||||
.stream()
|
||||
.findFirst()
|
||||
.orElseThrow(() ->
|
||||
new AssertionFailedError("Process Instance not found", bpmnProcessId, null));
|
||||
}
|
||||
|
||||
public static void hasPassedElement(String elementId, RecordStream recordStream, String bpmnProcessId) {
|
||||
processInstance(recordStream).withBpmnProcessId(bpmnProcessId)
|
||||
.withRejectionType(NULL_VAL)
|
||||
.withElementId(elementId)
|
||||
.stream()
|
||||
.findFirst()
|
||||
.orElseThrow(() ->
|
||||
new AssertionFailedError("Element not found", elementId, null));
|
||||
}
|
||||
|
||||
public static long getProcessInstanceId(RecordStream recordStream, String bpmnProcessId) {
|
||||
return getProcessInstance(recordStream, bpmnProcessId).getKey();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user