initial commit
This commit is contained in:
27
.github/workflows/build-and-publish.yaml
vendored
Normal file
27
.github/workflows/build-and-publish.yaml
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
name: Build & Push (Docker)
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- 'main'
|
||||
|
||||
jobs:
|
||||
build-and-push:
|
||||
name: Build JAR and push Docker image
|
||||
runs-on: ubuntu-18.04
|
||||
env:
|
||||
repo: lwluc/camunda-ddd-and-clean-architecture
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
- name: Set up JDK 17
|
||||
uses: actions/setup-java@v1
|
||||
with:
|
||||
java-version: 17
|
||||
- name: Maven Package
|
||||
run: mvn clean package
|
||||
- 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}} .
|
||||
- name: Publish Docker image
|
||||
run: docker push ${{env.repo}}
|
||||
48
.gitignore
vendored
Normal file
48
.gitignore
vendored
Normal file
@@ -0,0 +1,48 @@
|
||||
# Compiled class file
|
||||
*.class
|
||||
|
||||
# Log file
|
||||
*.log
|
||||
|
||||
# BlueJ files
|
||||
*.ctxt
|
||||
|
||||
# Mobile Tools for Java (J2ME)
|
||||
.mtj.tmp/
|
||||
|
||||
# Package Files #
|
||||
*.jar
|
||||
*.war
|
||||
*.nar
|
||||
*.ear
|
||||
*.zip
|
||||
*.tar.gz
|
||||
*.rar
|
||||
|
||||
# Maven
|
||||
log/
|
||||
target/
|
||||
|
||||
.classpath
|
||||
.settings
|
||||
.project
|
||||
|
||||
**.db
|
||||
|
||||
# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
|
||||
hs_err_pid*
|
||||
|
||||
# OS generated files #
|
||||
######################
|
||||
.DS_Store
|
||||
.DS_Store?
|
||||
._*
|
||||
.Spotlight-V100
|
||||
.Trashes
|
||||
ehthumbs.db
|
||||
Thumbs.db
|
||||
|
||||
# Intellij
|
||||
.idea/
|
||||
*.iml
|
||||
*.iws
|
||||
17
Dockerfile
Normal file
17
Dockerfile
Normal file
@@ -0,0 +1,17 @@
|
||||
FROM maven:3.8.5-openjdk-17 as build
|
||||
|
||||
COPY pom.xml .
|
||||
|
||||
RUN mvn -B dependency:go-offline
|
||||
|
||||
COPY src src
|
||||
|
||||
RUN mvn -B package
|
||||
|
||||
FROM openjdk:11-jre-slim-buster
|
||||
|
||||
COPY --from=build target/camunda-ddd-and-clean-architecture.jar .
|
||||
|
||||
EXPOSE 8080
|
||||
|
||||
ENTRYPOINT ["java", "-jar", "camunda-ddd-and-clean-architecture.jar"]
|
||||
54
README.md
Normal file
54
README.md
Normal file
@@ -0,0 +1,54 @@
|
||||
# Camunda DDD and Clean Architecture
|
||||
|
||||
An example to show how you could use clean architecture and DDD elements with Camunda.
|
||||
|
||||
## 🚀Features
|
||||
|
||||
The [BPMN process](./assets/loan_agreement.png) is a tiny process just to demonstrate the architecture.
|
||||
|
||||
With the following POST request you could start the process:
|
||||
|
||||
```curl
|
||||
curl --request POST \
|
||||
--url http://localhost:8080/loan/agreement/1 \
|
||||
--header 'Content-Type: application/json' \
|
||||
--cookie JSESSIONID=9E203C2691A2F320151C467311C720D1 \
|
||||
--data '
|
||||
"customerNumber": "A-11",
|
||||
"name": "Tester",
|
||||
"mailAddress": "tester@web.io",
|
||||
"amount": 1100
|
||||
}'
|
||||
```
|
||||
|
||||
Using the admin user (`username: admin` and `password: pw`) you could log in to the Camunda Cockpit.
|
||||
|
||||
## 🏗Architecture
|
||||
|
||||
In the following sections contains some small aspects explaining the advantages of Domain Driven Design (DDD) and clean architecture.
|
||||
|
||||

|
||||
|
||||
### DDD
|
||||
|
||||
Using Domain Drive Design or to be more precise tactical DDD you could be way more expressive and closer to your business domain. Beside that the focus in *immutability* and building object that know all about their *invariants* helps you to structure your code. Such DDD Elements can be found in our [domain-primitives](https://github.com/domain-primitives/domain-primitives-java) library.
|
||||
|
||||
Structuring your code functional and brining more context to your object with, e.g. Value Object does not only help you to keep your code expressive, it also helps keeping it close to you business as your BPMN model.
|
||||
|
||||
### Clean
|
||||
|
||||
Using clean architecture as architecture style combines perfectly with Domain Driven Design, because we completely focus on our domain.
|
||||
|
||||
Flexibility around your domain is the main focus I want to show you in this little example.
|
||||
|
||||
The main advantage is the independence of any framework. Due to this fact the architecture allows, compared to the conventional layer-architecture, exchange for example you Camunda Framework. So you could migrate to Camunda 8 without even touching your business code.
|
||||
|
||||
[The origin of clean architecture](https://blog.cleancoder.com/uncle-bob/2012/08/13/the-clean-architecture.html).
|
||||
|
||||
## 🙏🏼Credits
|
||||
|
||||
Thanks to [Matthias Eschhold](https://github.com/MatthiasEschhold) for the passionate discussion around DDD and clean architecture.
|
||||
|
||||
## 📨Contact
|
||||
|
||||
If you have any questions or ideas feel free to contact me or create an [issue](https://github.com/lwluc/camunda-ddd-and-clean-architecture/issues).
|
||||
536
assets/camunda-ddd-and-clean-architecture.excalidraw
Normal file
536
assets/camunda-ddd-and-clean-architecture.excalidraw
Normal file
@@ -0,0 +1,536 @@
|
||||
{
|
||||
"type": "excalidraw",
|
||||
"version": 2,
|
||||
"source": "https://excalidraw.com",
|
||||
"elements": [
|
||||
{
|
||||
"type": "ellipse",
|
||||
"version": 419,
|
||||
"versionNonce": 1353777024,
|
||||
"isDeleted": false,
|
||||
"id": "Euc_Y36uu1tj7wSxPMAKo",
|
||||
"fillStyle": "hachure",
|
||||
"strokeWidth": 1,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 1,
|
||||
"opacity": 100,
|
||||
"angle": 0,
|
||||
"x": 188.9761692951783,
|
||||
"y": 503.8009634020121,
|
||||
"strokeColor": "#000",
|
||||
"backgroundColor": "#f39000",
|
||||
"width": 626.3757259147652,
|
||||
"height": 626.3757259147652,
|
||||
"seed": 2072553600,
|
||||
"groupIds": [],
|
||||
"strokeSharpness": "sharp",
|
||||
"boundElements": [],
|
||||
"updated": 1651467138757,
|
||||
"link": null,
|
||||
"locked": false
|
||||
},
|
||||
{
|
||||
"id": "ufXZhHDFuihic0jU210X8",
|
||||
"type": "ellipse",
|
||||
"x": 257.6329680816313,
|
||||
"y": 579.0412908392208,
|
||||
"width": 481.53809559813783,
|
||||
"height": 481.53809559813783,
|
||||
"angle": 0,
|
||||
"strokeColor": "#000",
|
||||
"backgroundColor": "#1faf98",
|
||||
"fillStyle": "cross-hatch",
|
||||
"strokeWidth": 1,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 1,
|
||||
"opacity": 100,
|
||||
"groupIds": [],
|
||||
"strokeSharpness": "sharp",
|
||||
"seed": 744693632,
|
||||
"version": 236,
|
||||
"versionNonce": 1099552896,
|
||||
"isDeleted": false,
|
||||
"boundElements": null,
|
||||
"updated": 1651467092353,
|
||||
"link": null,
|
||||
"locked": false
|
||||
},
|
||||
{
|
||||
"type": "text",
|
||||
"version": 533,
|
||||
"versionNonce": 146319488,
|
||||
"isDeleted": false,
|
||||
"id": "hPRUDN6SwwxNALCFa6rYB",
|
||||
"fillStyle": "cross-hatch",
|
||||
"strokeWidth": 1,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 1,
|
||||
"opacity": 100,
|
||||
"angle": 0,
|
||||
"x": 431.9020158807,
|
||||
"y": 608.5664954874767,
|
||||
"strokeColor": "#000",
|
||||
"backgroundColor": "#ced4da",
|
||||
"width": 133,
|
||||
"height": 35,
|
||||
"seed": 1164144512,
|
||||
"groupIds": [],
|
||||
"strokeSharpness": "sharp",
|
||||
"boundElements": [],
|
||||
"updated": 1651467106457,
|
||||
"link": null,
|
||||
"locked": false,
|
||||
"fontSize": 28,
|
||||
"fontFamily": 1,
|
||||
"text": "Use Case",
|
||||
"baseline": 25,
|
||||
"textAlign": "center",
|
||||
"verticalAlign": "top",
|
||||
"containerId": null,
|
||||
"originalText": "Use Case"
|
||||
},
|
||||
{
|
||||
"type": "text",
|
||||
"version": 606,
|
||||
"versionNonce": 1956728704,
|
||||
"isDeleted": false,
|
||||
"id": "O6LsqUbz9_Git5cLAzGhJ",
|
||||
"fillStyle": "cross-hatch",
|
||||
"strokeWidth": 1,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 1,
|
||||
"opacity": 100,
|
||||
"angle": 0,
|
||||
"x": 444.78302406663033,
|
||||
"y": 525.8021353065467,
|
||||
"strokeColor": "#000",
|
||||
"backgroundColor": "#ced4da",
|
||||
"width": 111,
|
||||
"height": 35,
|
||||
"seed": 1772998528,
|
||||
"groupIds": [],
|
||||
"strokeSharpness": "sharp",
|
||||
"boundElements": [],
|
||||
"updated": 1651467142240,
|
||||
"link": null,
|
||||
"locked": false,
|
||||
"fontSize": 28,
|
||||
"fontFamily": 1,
|
||||
"text": "Adapter",
|
||||
"baseline": 25,
|
||||
"textAlign": "center",
|
||||
"verticalAlign": "top",
|
||||
"containerId": null,
|
||||
"originalText": "Adapter"
|
||||
},
|
||||
{
|
||||
"type": "ellipse",
|
||||
"version": 1001,
|
||||
"versionNonce": 1254697088,
|
||||
"isDeleted": false,
|
||||
"id": "DUaacDwmSlxMWxQc9k7RX",
|
||||
"fillStyle": "cross-hatch",
|
||||
"strokeWidth": 1,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 1,
|
||||
"opacity": 100,
|
||||
"angle": 0,
|
||||
"x": 339.3006834918491,
|
||||
"y": 669.1363581442278,
|
||||
"strokeColor": "#000000",
|
||||
"backgroundColor": "#fff",
|
||||
"width": 318.37543728901085,
|
||||
"height": 318.3754372890108,
|
||||
"seed": 984932224,
|
||||
"groupIds": [
|
||||
"B0twdI68Hz7VWMi4UlAOW"
|
||||
],
|
||||
"strokeSharpness": "sharp",
|
||||
"boundElements": [],
|
||||
"updated": 1651467104973,
|
||||
"link": null,
|
||||
"locked": false
|
||||
},
|
||||
{
|
||||
"type": "ellipse",
|
||||
"version": 914,
|
||||
"versionNonce": 1810304,
|
||||
"isDeleted": false,
|
||||
"id": "D4JhsJph47k2QoB9fNuJK",
|
||||
"fillStyle": "cross-hatch",
|
||||
"strokeWidth": 1,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 1,
|
||||
"opacity": 100,
|
||||
"angle": 0,
|
||||
"x": 338.8304314453659,
|
||||
"y": 669.2850979118147,
|
||||
"strokeColor": "#000000",
|
||||
"backgroundColor": "#36bcee",
|
||||
"width": 318.37543728901085,
|
||||
"height": 318.3754372890108,
|
||||
"seed": 522901632,
|
||||
"groupIds": [
|
||||
"B0twdI68Hz7VWMi4UlAOW"
|
||||
],
|
||||
"strokeSharpness": "sharp",
|
||||
"boundElements": [],
|
||||
"updated": 1651467104973,
|
||||
"link": null,
|
||||
"locked": false
|
||||
},
|
||||
{
|
||||
"type": "text",
|
||||
"version": 574,
|
||||
"versionNonce": 1485838464,
|
||||
"isDeleted": false,
|
||||
"id": "o0Agc55hL44xHW-ZToF4P",
|
||||
"fillStyle": "cross-hatch",
|
||||
"strokeWidth": 1,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 1,
|
||||
"opacity": 100,
|
||||
"angle": 0,
|
||||
"x": 445.9289062293193,
|
||||
"y": 695.1180406103032,
|
||||
"strokeColor": "#000",
|
||||
"backgroundColor": "#ced4da",
|
||||
"width": 107,
|
||||
"height": 35,
|
||||
"seed": 1609363328,
|
||||
"groupIds": [
|
||||
"B0twdI68Hz7VWMi4UlAOW"
|
||||
],
|
||||
"strokeSharpness": "sharp",
|
||||
"boundElements": [],
|
||||
"updated": 1651467104973,
|
||||
"link": null,
|
||||
"locked": false,
|
||||
"fontSize": 28,
|
||||
"fontFamily": 1,
|
||||
"text": "Entities",
|
||||
"baseline": 25,
|
||||
"textAlign": "center",
|
||||
"verticalAlign": "top",
|
||||
"containerId": null,
|
||||
"originalText": "Entities"
|
||||
},
|
||||
{
|
||||
"type": "rectangle",
|
||||
"version": 920,
|
||||
"versionNonce": 1249619840,
|
||||
"isDeleted": false,
|
||||
"id": "tYHC-hmS7pDvbvw9mqXFg",
|
||||
"fillStyle": "cross-hatch",
|
||||
"strokeWidth": 1,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 1,
|
||||
"opacity": 100,
|
||||
"angle": 0,
|
||||
"x": 443.939164744378,
|
||||
"y": 907.9454124118723,
|
||||
"strokeColor": "#000",
|
||||
"backgroundColor": "transparent",
|
||||
"width": 108,
|
||||
"height": 59,
|
||||
"seed": 356239488,
|
||||
"groupIds": [
|
||||
"B0twdI68Hz7VWMi4UlAOW"
|
||||
],
|
||||
"strokeSharpness": "sharp",
|
||||
"boundElements": [
|
||||
{
|
||||
"id": "ar58-5fnU7z_xVgO1qNsi",
|
||||
"type": "text"
|
||||
},
|
||||
{
|
||||
"type": "text",
|
||||
"id": "ar58-5fnU7z_xVgO1qNsi"
|
||||
}
|
||||
],
|
||||
"updated": 1651467104973,
|
||||
"link": null,
|
||||
"locked": false
|
||||
},
|
||||
{
|
||||
"type": "text",
|
||||
"version": 915,
|
||||
"versionNonce": 1878591616,
|
||||
"isDeleted": false,
|
||||
"id": "ar58-5fnU7z_xVgO1qNsi",
|
||||
"fillStyle": "cross-hatch",
|
||||
"strokeWidth": 1,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 1,
|
||||
"opacity": 100,
|
||||
"angle": 0,
|
||||
"x": 448.939164744378,
|
||||
"y": 917.4454124118723,
|
||||
"strokeColor": "#000",
|
||||
"backgroundColor": "#ced4da",
|
||||
"width": 98,
|
||||
"height": 40,
|
||||
"seed": 1134755712,
|
||||
"groupIds": [
|
||||
"B0twdI68Hz7VWMi4UlAOW"
|
||||
],
|
||||
"strokeSharpness": "sharp",
|
||||
"boundElements": [],
|
||||
"updated": 1651467104973,
|
||||
"link": null,
|
||||
"locked": false,
|
||||
"fontSize": 16,
|
||||
"fontFamily": 1,
|
||||
"text": "<Value \nObject>",
|
||||
"baseline": 34,
|
||||
"textAlign": "center",
|
||||
"verticalAlign": "middle",
|
||||
"containerId": "tYHC-hmS7pDvbvw9mqXFg",
|
||||
"originalText": "<Value Object>"
|
||||
},
|
||||
{
|
||||
"type": "rectangle",
|
||||
"version": 936,
|
||||
"versionNonce": 1851375488,
|
||||
"isDeleted": false,
|
||||
"id": "Un4YWq1C54B8PnUiK5agt",
|
||||
"fillStyle": "cross-hatch",
|
||||
"strokeWidth": 1,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 1,
|
||||
"opacity": 100,
|
||||
"angle": 0,
|
||||
"x": 512.2046968298423,
|
||||
"y": 838.9442405073372,
|
||||
"strokeColor": "#000",
|
||||
"backgroundColor": "transparent",
|
||||
"width": 108,
|
||||
"height": 59,
|
||||
"seed": 2075048064,
|
||||
"groupIds": [
|
||||
"B0twdI68Hz7VWMi4UlAOW"
|
||||
],
|
||||
"strokeSharpness": "sharp",
|
||||
"boundElements": [
|
||||
{
|
||||
"id": "tRDt4Vip1_Ois0oz3rbzx",
|
||||
"type": "text"
|
||||
},
|
||||
{
|
||||
"id": "tRDt4Vip1_Ois0oz3rbzx",
|
||||
"type": "text"
|
||||
},
|
||||
{
|
||||
"type": "text",
|
||||
"id": "tRDt4Vip1_Ois0oz3rbzx"
|
||||
}
|
||||
],
|
||||
"updated": 1651467104973,
|
||||
"link": null,
|
||||
"locked": false
|
||||
},
|
||||
{
|
||||
"type": "text",
|
||||
"version": 923,
|
||||
"versionNonce": 214169728,
|
||||
"isDeleted": false,
|
||||
"id": "tRDt4Vip1_Ois0oz3rbzx",
|
||||
"fillStyle": "cross-hatch",
|
||||
"strokeWidth": 1,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 1,
|
||||
"opacity": 100,
|
||||
"angle": 0,
|
||||
"x": 517.2046968298423,
|
||||
"y": 858.4442405073372,
|
||||
"strokeColor": "#000",
|
||||
"backgroundColor": "#ced4da",
|
||||
"width": 98,
|
||||
"height": 20,
|
||||
"seed": 1712787328,
|
||||
"groupIds": [
|
||||
"B0twdI68Hz7VWMi4UlAOW"
|
||||
],
|
||||
"strokeSharpness": "sharp",
|
||||
"boundElements": [],
|
||||
"updated": 1651467104973,
|
||||
"link": null,
|
||||
"locked": false,
|
||||
"fontSize": 16,
|
||||
"fontFamily": 1,
|
||||
"text": "<Entity>",
|
||||
"baseline": 14,
|
||||
"textAlign": "center",
|
||||
"verticalAlign": "middle",
|
||||
"containerId": "Un4YWq1C54B8PnUiK5agt",
|
||||
"originalText": "<Entity>"
|
||||
},
|
||||
{
|
||||
"type": "rectangle",
|
||||
"version": 966,
|
||||
"versionNonce": 200658816,
|
||||
"isDeleted": false,
|
||||
"id": "dfPlLg74polPs4cRMjLMH",
|
||||
"fillStyle": "cross-hatch",
|
||||
"strokeWidth": 1,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 1,
|
||||
"opacity": 100,
|
||||
"angle": 0,
|
||||
"x": 387.1176524654826,
|
||||
"y": 839.8847446003026,
|
||||
"strokeColor": "#000",
|
||||
"backgroundColor": "transparent",
|
||||
"width": 108,
|
||||
"height": 59,
|
||||
"seed": 1984145536,
|
||||
"groupIds": [
|
||||
"B0twdI68Hz7VWMi4UlAOW"
|
||||
],
|
||||
"strokeSharpness": "sharp",
|
||||
"boundElements": [
|
||||
{
|
||||
"id": "nF3MdAxVTmtMBuLT2YClm",
|
||||
"type": "text"
|
||||
},
|
||||
{
|
||||
"id": "nF3MdAxVTmtMBuLT2YClm",
|
||||
"type": "text"
|
||||
},
|
||||
{
|
||||
"id": "nF3MdAxVTmtMBuLT2YClm",
|
||||
"type": "text"
|
||||
},
|
||||
{
|
||||
"type": "text",
|
||||
"id": "nF3MdAxVTmtMBuLT2YClm"
|
||||
}
|
||||
],
|
||||
"updated": 1651467104973,
|
||||
"link": null,
|
||||
"locked": false
|
||||
},
|
||||
{
|
||||
"type": "text",
|
||||
"version": 961,
|
||||
"versionNonce": 850454656,
|
||||
"isDeleted": false,
|
||||
"id": "nF3MdAxVTmtMBuLT2YClm",
|
||||
"fillStyle": "cross-hatch",
|
||||
"strokeWidth": 1,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 1,
|
||||
"opacity": 100,
|
||||
"angle": 0,
|
||||
"x": 392.1176524654826,
|
||||
"y": 859.3847446003026,
|
||||
"strokeColor": "#000",
|
||||
"backgroundColor": "#ced4da",
|
||||
"width": 98,
|
||||
"height": 20,
|
||||
"seed": 1065951104,
|
||||
"groupIds": [
|
||||
"B0twdI68Hz7VWMi4UlAOW"
|
||||
],
|
||||
"strokeSharpness": "sharp",
|
||||
"boundElements": [],
|
||||
"updated": 1651467104973,
|
||||
"link": null,
|
||||
"locked": false,
|
||||
"fontSize": 16,
|
||||
"fontFamily": 1,
|
||||
"text": "<Aggregate>",
|
||||
"baseline": 14,
|
||||
"textAlign": "center",
|
||||
"verticalAlign": "middle",
|
||||
"containerId": "dfPlLg74polPs4cRMjLMH",
|
||||
"originalText": "<Aggregate>"
|
||||
},
|
||||
{
|
||||
"type": "rectangle",
|
||||
"version": 985,
|
||||
"versionNonce": 999284608,
|
||||
"isDeleted": false,
|
||||
"id": "IHNH37dUIbqk92l6XDzjC",
|
||||
"fillStyle": "cross-hatch",
|
||||
"strokeWidth": 1,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 1,
|
||||
"opacity": 100,
|
||||
"angle": 0,
|
||||
"x": 444.488402136354,
|
||||
"y": 749.5963516756522,
|
||||
"strokeColor": "#000",
|
||||
"backgroundColor": "transparent",
|
||||
"width": 108,
|
||||
"height": 59,
|
||||
"seed": 1348943744,
|
||||
"groupIds": [
|
||||
"B0twdI68Hz7VWMi4UlAOW"
|
||||
],
|
||||
"strokeSharpness": "sharp",
|
||||
"boundElements": [
|
||||
{
|
||||
"id": "w0udDoif6KaJf9if4gBJ-",
|
||||
"type": "text"
|
||||
},
|
||||
{
|
||||
"id": "w0udDoif6KaJf9if4gBJ-",
|
||||
"type": "text"
|
||||
},
|
||||
{
|
||||
"id": "w0udDoif6KaJf9if4gBJ-",
|
||||
"type": "text"
|
||||
},
|
||||
{
|
||||
"type": "text",
|
||||
"id": "w0udDoif6KaJf9if4gBJ-"
|
||||
}
|
||||
],
|
||||
"updated": 1651467104973,
|
||||
"link": null,
|
||||
"locked": false
|
||||
},
|
||||
{
|
||||
"type": "text",
|
||||
"version": 978,
|
||||
"versionNonce": 1837095040,
|
||||
"isDeleted": false,
|
||||
"id": "w0udDoif6KaJf9if4gBJ-",
|
||||
"fillStyle": "cross-hatch",
|
||||
"strokeWidth": 1,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 1,
|
||||
"opacity": 100,
|
||||
"angle": 0,
|
||||
"x": 449.488402136354,
|
||||
"y": 769.0963516756522,
|
||||
"strokeColor": "#000",
|
||||
"backgroundColor": "#ced4da",
|
||||
"width": 98,
|
||||
"height": 20,
|
||||
"seed": 1011775616,
|
||||
"groupIds": [
|
||||
"B0twdI68Hz7VWMi4UlAOW"
|
||||
],
|
||||
"strokeSharpness": "sharp",
|
||||
"boundElements": [],
|
||||
"updated": 1651467104973,
|
||||
"link": null,
|
||||
"locked": false,
|
||||
"fontSize": 16,
|
||||
"fontFamily": 1,
|
||||
"text": "<Service>",
|
||||
"baseline": 14,
|
||||
"textAlign": "center",
|
||||
"verticalAlign": "middle",
|
||||
"containerId": "IHNH37dUIbqk92l6XDzjC",
|
||||
"originalText": "<Service>"
|
||||
}
|
||||
],
|
||||
"appState": {
|
||||
"gridSize": null,
|
||||
"viewBackgroundColor": "#ffffff"
|
||||
},
|
||||
"files": {}
|
||||
}
|
||||
BIN
assets/camunda-ddd-and-clean-architecture.png
Normal file
BIN
assets/camunda-ddd-and-clean-architecture.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 5.7 MiB |
BIN
assets/loan_agreement.png
Normal file
BIN
assets/loan_agreement.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 186 KiB |
138
pom.xml
Normal file
138
pom.xml
Normal file
@@ -0,0 +1,138 @@
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<groupId>de.weinbrecht.luc.bpm.architecture</groupId>
|
||||
<artifactId>camunda-ddd-and-clean-architecture</artifactId>
|
||||
<version>1.0.0-SNAPSHOT</version>
|
||||
|
||||
<properties>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
<maven.compiler.source>17</maven.compiler.source>
|
||||
<maven.compiler.target>17</maven.compiler.target>
|
||||
|
||||
<version.junit5>5.8.2</version.junit5>
|
||||
<version.lombok>1.18.24</version.lombok>
|
||||
<version.domainprimitives>0.1.0</version.domainprimitives>
|
||||
<version.bpmAssert>1.1.0</version.bpmAssert>
|
||||
<version.camundaMockito>6.17.0</version.camundaMockito>
|
||||
</properties>
|
||||
|
||||
<dependencyManagement>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-dependencies</artifactId>
|
||||
<version>2.6.4</version>
|
||||
<type>pom</type>
|
||||
<scope>import</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.camunda.bpm</groupId>
|
||||
<artifactId>camunda-bom</artifactId>
|
||||
<version>7.17.0</version>
|
||||
<scope>import</scope>
|
||||
<type>pom</type>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</dependencyManagement>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.camunda.bpm.springboot</groupId>
|
||||
<artifactId>camunda-bpm-spring-boot-starter-rest</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.camunda.bpm.springboot</groupId>
|
||||
<artifactId>camunda-bpm-spring-boot-starter-webapp</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.camunda.bpm.springboot</groupId>
|
||||
<artifactId>camunda-bpm-spring-boot-starter-test</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.h2database</groupId>
|
||||
<artifactId>h2</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-jdbc</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-data-jpa</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.github.domain-primitives</groupId>
|
||||
<artifactId>domainprimitives-java</artifactId>
|
||||
<version>${version.domainprimitives}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
<version>${version.lombok}</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.junit.jupiter</groupId>
|
||||
<artifactId>junit-jupiter-api</artifactId>
|
||||
<version>${version.junit5}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.junit.jupiter</groupId>
|
||||
<artifactId>junit-jupiter-params</artifactId>
|
||||
<version>${version.junit5}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.junit.jupiter</groupId>
|
||||
<artifactId>junit-jupiter-engine</artifactId>
|
||||
<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>
|
||||
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<finalName>${artifactId}</finalName>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||
<version>2.6.4</version>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
</project>
|
||||
@@ -0,0 +1,13 @@
|
||||
package de.weinbrecht.luc.bpm.architecture;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
|
||||
@SpringBootApplication
|
||||
public class Application {
|
||||
|
||||
public static void main(String... args) {
|
||||
SpringApplication.run(Application.class, args);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package de.weinbrecht.luc.bpm.architecture.common;
|
||||
|
||||
public class ProcessConstants {
|
||||
public static final String PROCESS_DEFINITION = "Loan_Agreement";
|
||||
|
||||
public static final String START_EVENT_MESSAGE_REF = "loanAgreementReceivedMessage";
|
||||
|
||||
|
||||
public static final String LOAN_AGREEMENT_NUMBER = "loanAgreementNumber";
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
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 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.common.ProcessConstants.LOAN_AGREEMENT_NUMBER;
|
||||
|
||||
@Slf4j
|
||||
@RequiredArgsConstructor
|
||||
@Component
|
||||
public class ApproveLoanAgreement implements JavaDelegate {
|
||||
|
||||
private final LoanAgreementStatusCommand loanAgreementStatusCommand;
|
||||
|
||||
@Override
|
||||
public void execute(DelegateExecution delegateExecution) {
|
||||
Long loanAgreementNumber = (Long) delegateExecution.getVariable(LOAN_AGREEMENT_NUMBER);
|
||||
loanAgreementStatusCommand.accept(new LoanAgreementNumber(loanAgreementNumber));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
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 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.common.ProcessConstants.LOAN_AGREEMENT_NUMBER;
|
||||
|
||||
@Slf4j
|
||||
@RequiredArgsConstructor
|
||||
@Component
|
||||
public class RejectionLoanAgreement implements JavaDelegate {
|
||||
|
||||
private final LoanAgreementStatusCommand loanAgreementStatusCommand;
|
||||
|
||||
@Override
|
||||
public void execute(DelegateExecution delegateExecution) {
|
||||
Long loanAgreementNumber = (Long) delegateExecution.getVariable(LOAN_AGREEMENT_NUMBER);
|
||||
loanAgreementStatusCommand.reject(new LoanAgreementNumber(loanAgreementNumber));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package de.weinbrecht.luc.bpm.architecture.loan.agreement.adapter.in.web;
|
||||
|
||||
import de.weinbrecht.luc.bpm.architecture.loan.agreement.domain.model.CaseId;
|
||||
import de.weinbrecht.luc.bpm.architecture.loan.agreement.usecase.in.LoanAgreementCreation;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
@RequiredArgsConstructor
|
||||
@RestController
|
||||
@RequestMapping("loan/agreement/")
|
||||
class LoanAgreementController {
|
||||
|
||||
private final LoanAgreementCreation loanAgreementCreation;
|
||||
private final LoanAgreementMapper mapper;
|
||||
|
||||
@PostMapping("{caseId}")
|
||||
public void create(@RequestBody LoanAgreementResource loanAgreementResource,
|
||||
@PathVariable String caseId) {
|
||||
loanAgreementCreation.create(mapper.mapToDomain(loanAgreementResource), new CaseId(caseId));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package de.weinbrecht.luc.bpm.architecture.loan.agreement.adapter.in.web;
|
||||
|
||||
import de.weinbrecht.luc.bpm.architecture.loan.agreement.domain.model.Amount;
|
||||
import de.weinbrecht.luc.bpm.architecture.loan.agreement.domain.model.CustomerNumber;
|
||||
import de.weinbrecht.luc.bpm.architecture.loan.agreement.domain.model.LoanAgreement;
|
||||
import de.weinbrecht.luc.bpm.architecture.loan.agreement.domain.model.Recipient;
|
||||
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 org.springframework.stereotype.Component;
|
||||
|
||||
@Component("LoanAgreementMapperWeb")
|
||||
class LoanAgreementMapper {
|
||||
public LoanAgreement mapToDomain(LoanAgreementResource loanAgreementResource) {
|
||||
return new LoanAgreement(
|
||||
new Recipient(
|
||||
new CustomerNumber(loanAgreementResource.getCustomerNumber()),
|
||||
new Name(loanAgreementResource.getName()),
|
||||
new MailAddress(loanAgreementResource.getMailAddress())
|
||||
),
|
||||
new Amount(loanAgreementResource.getAmount())
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package de.weinbrecht.luc.bpm.architecture.loan.agreement.adapter.in.web;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
class LoanAgreementResource {
|
||||
private String customerNumber;
|
||||
private String name;
|
||||
private String mailAddress;
|
||||
private Integer amount;
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package de.weinbrecht.luc.bpm.architecture.loan.agreement.adapter.out.db;
|
||||
|
||||
import org.springframework.data.repository.CrudRepository;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
@Repository
|
||||
public interface LoanAgreementCRUDRepository extends CrudRepository<LoanAgreementEntity, Long> {
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package de.weinbrecht.luc.bpm.architecture.loan.agreement.adapter.out.db;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.GeneratedValue;
|
||||
import javax.persistence.Id;
|
||||
|
||||
import static javax.persistence.GenerationType.AUTO;
|
||||
|
||||
@Data
|
||||
@Entity
|
||||
class LoanAgreementEntity {
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy= AUTO)
|
||||
private Long id;
|
||||
private Integer amount;
|
||||
private String customerNumber;
|
||||
private String name;
|
||||
private String mailAddress;
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package de.weinbrecht.luc.bpm.architecture.loan.agreement.adapter.out.db;
|
||||
|
||||
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 org.springframework.stereotype.Component;
|
||||
|
||||
@Component("LoanAgreementMapperDb")
|
||||
class LoanAgreementMapper {
|
||||
public LoanAgreementEntity mapToDb(LoanAgreement loanAgreement) {
|
||||
LoanAgreementEntity loanAgreementEntity = new LoanAgreementEntity();
|
||||
loanAgreementEntity.setAmount(loanAgreement.getAmount().getValue());
|
||||
Recipient recipient = loanAgreement.getRecipient();
|
||||
loanAgreementEntity.setCustomerNumber(recipient.getCustomerNumber().getValue());
|
||||
loanAgreementEntity.setName(recipient.getName().getValue());
|
||||
loanAgreementEntity.setMailAddress(recipient.getMailAddress().getValue());
|
||||
return loanAgreementEntity;
|
||||
}
|
||||
|
||||
public LoanAgreement mapToDomain(LoanAgreementEntity loanAgreementEntity) {
|
||||
return new LoanAgreement(
|
||||
new LoanAgreementNumber(loanAgreementEntity.getId()),
|
||||
new Recipient(
|
||||
new CustomerNumber(loanAgreementEntity.getCustomerNumber()),
|
||||
new Name(loanAgreementEntity.getName()),
|
||||
new MailAddress(loanAgreementEntity.getMailAddress())
|
||||
),
|
||||
new Amount(loanAgreementEntity.getAmount())
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package de.weinbrecht.luc.bpm.architecture.loan.agreement.adapter.out.db;
|
||||
|
||||
class LoanAgreementNotFoundException extends RuntimeException {
|
||||
public LoanAgreementNotFoundException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package de.weinbrecht.luc.bpm.architecture.loan.agreement.adapter.out.db;
|
||||
|
||||
import de.weinbrecht.luc.bpm.architecture.loan.agreement.domain.model.LoanAgreement;
|
||||
import de.weinbrecht.luc.bpm.architecture.loan.agreement.domain.model.LoanAgreementNumber;
|
||||
import de.weinbrecht.luc.bpm.architecture.loan.agreement.usecase.out.LoanAgreementCommand;
|
||||
import de.weinbrecht.luc.bpm.architecture.loan.agreement.usecase.out.LoanAgreementQuery;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import static java.lang.String.format;
|
||||
|
||||
@RequiredArgsConstructor
|
||||
@Component
|
||||
class LoanAgreementRepository implements LoanAgreementCommand, LoanAgreementQuery {
|
||||
|
||||
private final LoanAgreementCRUDRepository crudRepository;
|
||||
private final LoanAgreementMapper mapper;
|
||||
|
||||
@Override
|
||||
public LoanAgreementNumber save(LoanAgreement loanAgreement) {
|
||||
LoanAgreementEntity savedLoanAgreement = crudRepository.save(mapper.mapToDb(loanAgreement));
|
||||
return new LoanAgreementNumber(savedLoanAgreement.getId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public LoanAgreement loadByNumber(LoanAgreementNumber loanAgreementNumber) {
|
||||
return crudRepository.findById(loanAgreementNumber.getValue())
|
||||
.map(mapper::mapToDomain)
|
||||
.orElseThrow(() -> new LoanAgreementNotFoundException(
|
||||
format("Could not find loan agreement to number %s", loanAgreementNumber.getValue())));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package de.weinbrecht.luc.bpm.architecture.loan.agreement.adapter.out.legacy;
|
||||
|
||||
import de.weinbrecht.luc.bpm.architecture.loan.agreement.domain.model.LoanAgreement;
|
||||
import de.weinbrecht.luc.bpm.architecture.loan.agreement.usecase.out.LoanAgreementDistributor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Slf4j
|
||||
@Component
|
||||
public class LoanAgreementDistributorClient implements LoanAgreementDistributor {
|
||||
@Override
|
||||
public void sendLoanAgreement(LoanAgreement loanAgreement, boolean accepted) {
|
||||
log.info("Sending loan agreement with number {} and status [{}] to legacy system",
|
||||
loanAgreement.getLoanAgreementNumber().getValue(), accepted);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
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.ProcessEngineCommand;
|
||||
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.common.ProcessConstants.LOAN_AGREEMENT_NUMBER;
|
||||
import static de.weinbrecht.luc.bpm.architecture.common.ProcessConstants.START_EVENT_MESSAGE_REF;
|
||||
|
||||
@RequiredArgsConstructor
|
||||
@Component
|
||||
class ProcessEngineClient implements ProcessEngineCommand {
|
||||
|
||||
private final RuntimeService runtimeService;
|
||||
|
||||
@Override
|
||||
public void startLoanAgreement(CaseId caseId, LoanAgreementNumber loanAgreementNumber) {
|
||||
Map<String, Object> processVariables = new HashMap<>();
|
||||
processVariables.put(LOAN_AGREEMENT_NUMBER, loanAgreementNumber.getValue());
|
||||
runtimeService.startProcessInstanceByMessage(START_EVENT_MESSAGE_REF, caseId.getValue(), processVariables);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package de.weinbrecht.luc.bpm.architecture.loan.agreement.adapter.out.process;
|
||||
|
||||
class ProcessInstanceNotFoundException extends RuntimeException {
|
||||
public ProcessInstanceNotFoundException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package de.weinbrecht.luc.bpm.architecture.loan.agreement.domain.model;
|
||||
|
||||
import io.github.domainprimitives.type.ValueObject;
|
||||
|
||||
import static io.github.domainprimitives.validation.Constraints.isGreatThanOrEqual;
|
||||
|
||||
|
||||
public class Amount extends ValueObject<Integer> {
|
||||
public Amount(Integer value) {
|
||||
super(value, isGreatThanOrEqual(100));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (o != null && this.getClass() == o.getClass()) {
|
||||
ValueObject<Integer> valueObject = (ValueObject)o;
|
||||
return (valueObject.getValue()).equals(this.getValue());
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package de.weinbrecht.luc.bpm.architecture.loan.agreement.domain.model;
|
||||
|
||||
import io.github.domainprimitives.type.ValueObject;
|
||||
|
||||
import static io.github.domainprimitives.validation.Constraints.isNotBlank;
|
||||
|
||||
public class CaseId extends ValueObject<String> {
|
||||
public CaseId(String value) {
|
||||
super(value, isNotBlank());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package de.weinbrecht.luc.bpm.architecture.loan.agreement.domain.model;
|
||||
|
||||
import io.github.domainprimitives.type.ValueObject;
|
||||
|
||||
import static io.github.domainprimitives.validation.Constraints.isNotBlank;
|
||||
|
||||
public class CustomerNumber extends ValueObject<String> {
|
||||
public CustomerNumber(String value) {
|
||||
super(value, isNotBlank());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package de.weinbrecht.luc.bpm.architecture.loan.agreement.domain.model;
|
||||
|
||||
import io.github.domainprimitives.object.Aggregate;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.Getter;
|
||||
|
||||
@Getter
|
||||
@EqualsAndHashCode(callSuper = false)
|
||||
public class LoanAgreement extends Aggregate {
|
||||
|
||||
private LoanAgreementNumber loanAgreementNumber;
|
||||
private final Recipient recipient;
|
||||
private final Amount amount;
|
||||
|
||||
public LoanAgreement(Recipient recipient, Amount amount) {
|
||||
this.recipient = recipient;
|
||||
this.amount = amount;
|
||||
this.validate();
|
||||
}
|
||||
|
||||
public LoanAgreement(LoanAgreementNumber loanAgreementNumber, Recipient recipient, Amount amount) {
|
||||
this(recipient, amount);
|
||||
this.loanAgreementNumber = loanAgreementNumber;
|
||||
|
||||
validateNotNull(loanAgreementNumber, "Loan Agreement Number");
|
||||
evaluateValidations();
|
||||
}
|
||||
|
||||
protected void validate() {
|
||||
validateNotNull(recipient, "Recipient");
|
||||
validateNotNull(amount, "Loan Amount");
|
||||
evaluateValidations();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package de.weinbrecht.luc.bpm.architecture.loan.agreement.domain.model;
|
||||
|
||||
import io.github.domainprimitives.type.ValueObject;
|
||||
|
||||
import static io.github.domainprimitives.validation.Constraints.isNotNullLong;
|
||||
|
||||
public class LoanAgreementNumber extends ValueObject<Long> {
|
||||
public LoanAgreementNumber(Long value) {
|
||||
super(value, isNotNullLong());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package 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 io.github.domainprimitives.object.ComposedValueObject;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.Getter;
|
||||
|
||||
@Getter
|
||||
@EqualsAndHashCode(callSuper = false)
|
||||
public class Recipient extends ComposedValueObject {
|
||||
|
||||
private final CustomerNumber customerNumber;
|
||||
private final Name name;
|
||||
private final MailAddress mailAddress;
|
||||
|
||||
public Recipient(CustomerNumber customerNumber, Name name, MailAddress mailAddress) {
|
||||
this.customerNumber = customerNumber;
|
||||
this.name = name;
|
||||
this.mailAddress = mailAddress;
|
||||
this.validate();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void validate() {
|
||||
validateNotNull(customerNumber, "Customer Number");
|
||||
validateNotNull(name, "Name");
|
||||
validateNotNull(mailAddress, "Mail Address");
|
||||
evaluateValidations();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package de.weinbrecht.luc.bpm.architecture.loan.agreement.domain.model.recipient;
|
||||
|
||||
import io.github.domainprimitives.type.ValueObject;
|
||||
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import static io.github.domainprimitives.validation.Constraints.isPattern;
|
||||
|
||||
public class MailAddress extends ValueObject<String> {
|
||||
|
||||
public static final String VALID_EMAIL_ADDRESS_REGEX =
|
||||
Pattern.compile("^[a-zA-Z0-9_!#$%&’*+=?`{|}~^.-]+@[a-zA-Z0-9.-]+$",
|
||||
Pattern.CASE_INSENSITIVE).pattern();
|
||||
|
||||
|
||||
public MailAddress(String value) {
|
||||
super(value, isPattern(VALID_EMAIL_ADDRESS_REGEX));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package de.weinbrecht.luc.bpm.architecture.loan.agreement.domain.model.recipient;
|
||||
|
||||
import io.github.domainprimitives.type.ValueObject;
|
||||
|
||||
import static io.github.domainprimitives.validation.Constraints.hasMinLength;
|
||||
|
||||
public class Name extends ValueObject<String> {
|
||||
public Name(String value) {
|
||||
super(value, hasMinLength(3));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package de.weinbrecht.luc.bpm.architecture.loan.agreement.domain.service;
|
||||
|
||||
public class LoanAgreementException extends RuntimeException {
|
||||
|
||||
public LoanAgreementException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public LoanAgreementException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
package de.weinbrecht.luc.bpm.architecture.loan.agreement.domain.service;
|
||||
|
||||
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.domain.model.LoanAgreementNumber;
|
||||
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.LoanAgreementDistributor;
|
||||
import de.weinbrecht.luc.bpm.architecture.loan.agreement.usecase.out.LoanAgreementQuery;
|
||||
import de.weinbrecht.luc.bpm.architecture.loan.agreement.usecase.out.ProcessEngineCommand;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class LoanAgreementService implements LoanAgreementCreation, LoanAgreementStatusCommand {
|
||||
|
||||
private final LoanAgreementCommand loanAgreementCommand;
|
||||
private final ProcessEngineCommand processEngineCommand;
|
||||
private final LoanAgreementDistributor loanAgreementDistributor;
|
||||
private final LoanAgreementQuery loanAgreementQuery;
|
||||
|
||||
@Override
|
||||
public void create(LoanAgreement loanAgreement, CaseId caseId) {
|
||||
try {
|
||||
LoanAgreementNumber loanAgreementNumber = loanAgreementCommand.save(loanAgreement);
|
||||
processEngineCommand.startLoanAgreement(caseId, loanAgreementNumber);
|
||||
} catch (Exception e) {
|
||||
throw new LoanAgreementException("Cloud not save the loan agreement", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void accept(LoanAgreementNumber loanAgreementNumber) {
|
||||
loanAgreementDistributor.sendLoanAgreement(loanAgreementQuery.loadByNumber(loanAgreementNumber), true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reject(LoanAgreementNumber loanAgreementNumber) {
|
||||
loanAgreementDistributor.sendLoanAgreement(loanAgreementQuery.loadByNumber(loanAgreementNumber), false);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package de.weinbrecht.luc.bpm.architecture.loan.agreement.usecase.in;
|
||||
|
||||
import de.weinbrecht.luc.bpm.architecture.loan.agreement.domain.model.CaseId;
|
||||
import de.weinbrecht.luc.bpm.architecture.loan.agreement.domain.model.LoanAgreement;
|
||||
|
||||
public interface LoanAgreementCreation {
|
||||
void create(LoanAgreement loanAgreement, CaseId caseId);
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package de.weinbrecht.luc.bpm.architecture.loan.agreement.usecase.in;
|
||||
|
||||
import de.weinbrecht.luc.bpm.architecture.loan.agreement.domain.model.LoanAgreementNumber;
|
||||
|
||||
public interface LoanAgreementStatusCommand {
|
||||
void accept(LoanAgreementNumber loanAgreementNumber);
|
||||
void reject(LoanAgreementNumber loanAgreementNumber);
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package de.weinbrecht.luc.bpm.architecture.loan.agreement.usecase.out;
|
||||
|
||||
import de.weinbrecht.luc.bpm.architecture.loan.agreement.domain.model.LoanAgreement;
|
||||
import de.weinbrecht.luc.bpm.architecture.loan.agreement.domain.model.LoanAgreementNumber;
|
||||
|
||||
public interface LoanAgreementCommand {
|
||||
LoanAgreementNumber save(LoanAgreement loanAgreement);
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package de.weinbrecht.luc.bpm.architecture.loan.agreement.usecase.out;
|
||||
|
||||
import de.weinbrecht.luc.bpm.architecture.loan.agreement.domain.model.LoanAgreement;
|
||||
|
||||
public interface LoanAgreementDistributor {
|
||||
void sendLoanAgreement(LoanAgreement loanAgreement, boolean accepted);
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package de.weinbrecht.luc.bpm.architecture.loan.agreement.usecase.out;
|
||||
|
||||
import de.weinbrecht.luc.bpm.architecture.loan.agreement.domain.model.LoanAgreement;
|
||||
import de.weinbrecht.luc.bpm.architecture.loan.agreement.domain.model.LoanAgreementNumber;
|
||||
|
||||
public interface LoanAgreementQuery {
|
||||
LoanAgreement loadByNumber(LoanAgreementNumber loanAgreementNumber);
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package de.weinbrecht.luc.bpm.architecture.loan.agreement.usecase.out;
|
||||
|
||||
import de.weinbrecht.luc.bpm.architecture.loan.agreement.domain.model.CaseId;
|
||||
import de.weinbrecht.luc.bpm.architecture.loan.agreement.domain.model.LoanAgreementNumber;
|
||||
|
||||
public interface ProcessEngineCommand {
|
||||
void startLoanAgreement(CaseId caseId, LoanAgreementNumber loanAgreementNumber);
|
||||
}
|
||||
12
src/main/resources/application.yaml
Normal file
12
src/main/resources/application.yaml
Normal file
@@ -0,0 +1,12 @@
|
||||
spring.datasource.url: jdbc:h2:file:./camunda-h2-database
|
||||
spring:
|
||||
jpa:
|
||||
hibernate:
|
||||
ddl-auto: create-drop
|
||||
|
||||
camunda.bpm.admin-user:
|
||||
id: admin
|
||||
password: pw
|
||||
generic-properties:
|
||||
properties:
|
||||
initializeTelemetry: false
|
||||
36
src/main/resources/approve_agreement.dmn
Normal file
36
src/main/resources/approve_agreement.dmn
Normal file
@@ -0,0 +1,36 @@
|
||||
<?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">
|
||||
<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">
|
||||
<text>loanAgreementNumber</text>
|
||||
</inputExpression>
|
||||
</input>
|
||||
<output id="Output_1" name="approved" typeRef="boolean" biodi:width="192" />
|
||||
<rule id="DecisionRule_0p9ijdl">
|
||||
<inputEntry id="UnaryTests_1qou5p9">
|
||||
<text>>= 5</text>
|
||||
</inputEntry>
|
||||
<outputEntry id="LiteralExpression_1hlf608">
|
||||
<text>false</text>
|
||||
</outputEntry>
|
||||
</rule>
|
||||
<rule id="DecisionRule_1arucld">
|
||||
<inputEntry id="UnaryTests_1vqubek">
|
||||
<text>< 5</text>
|
||||
</inputEntry>
|
||||
<outputEntry id="LiteralExpression_1ovr1wt">
|
||||
<text>true</text>
|
||||
</outputEntry>
|
||||
</rule>
|
||||
</decisionTable>
|
||||
</decision>
|
||||
<dmndi:DMNDI>
|
||||
<dmndi:DMNDiagram>
|
||||
<dmndi:DMNShape dmnElementRef="approvement-check">
|
||||
<dc:Bounds height="80" width="180" x="160" y="100" />
|
||||
</dmndi:DMNShape>
|
||||
</dmndi:DMNDiagram>
|
||||
</dmndi:DMNDI>
|
||||
</definitions>
|
||||
142
src/main/resources/loan_agreement.bpmn
Normal file
142
src/main/resources/loan_agreement.bpmn
Normal file
@@ -0,0 +1,142 @@
|
||||
<?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: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:endEvent id="LoanAgreementNotApprovedEndEvent" name="loan agreement not approved">
|
||||
<bpmn:incoming>Flow_0eif63m</bpmn:incoming>
|
||||
</bpmn:endEvent>
|
||||
<bpmn:endEvent id="LoanAgreementApprovedEndEvent" name="loan agreement approved">
|
||||
<bpmn:incoming>Flow_1uqmps7</bpmn:incoming>
|
||||
</bpmn:endEvent>
|
||||
<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:serviceTask id="RejectLoanAgreementServiceTask" name="Rejection loan agreement" camunda:delegateExpression="${rejectionLoanAgreement}">
|
||||
<bpmn:incoming>Flow_0xpo6jp</bpmn:incoming>
|
||||
<bpmn:outgoing>Flow_0eif63m</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>
|
||||
<bpmn:sequenceFlow id="Flow_0eif63m" sourceRef="RejectLoanAgreementServiceTask" targetRef="LoanAgreementNotApprovedEndEvent" />
|
||||
<bpmn:sequenceFlow id="Flow_1uqmps7" sourceRef="ApproveLoanAgreementServiceTask" targetRef="LoanAgreementApprovedEndEvent" />
|
||||
<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:process>
|
||||
<bpmn:message id="Message_0o102df" 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="720" height="250" />
|
||||
<bpmndi:BPMNLabel />
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNEdge id="Flow_0eif63m_di" bpmnElement="Flow_0eif63m">
|
||||
<di:waypoint x="700" y="360" />
|
||||
<di:waypoint x="772" y="360" />
|
||||
</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>
|
||||
<bpmndi:BPMNEdge id="Flow_1hri7xc_di" bpmnElement="Flow_1hri7xc">
|
||||
<di:waypoint x="535" y="237" />
|
||||
<di:waypoint x="600" y="237" />
|
||||
<bpmndi:BPMNLabel>
|
||||
<dc:Bounds x="559" y="219" width="18" height="14" />
|
||||
</bpmndi:BPMNLabel>
|
||||
</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:BPMNLabel>
|
||||
<dc:Bounds x="519" y="308" width="13" height="14" />
|
||||
</bpmndi:BPMNLabel>
|
||||
</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>
|
||||
<bpmndi:BPMNEdge id="Flow_1rrsueh_di" bpmnElement="Flow_1rrsueh">
|
||||
<di:waypoint x="258" y="237" />
|
||||
<di:waypoint x="330" y="237" />
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNShape id="Activity_1244zx8_di" bpmnElement="ApproveAgreementRuleTask">
|
||||
<dc:Bounds x="330" y="197" 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:BPMNLabel>
|
||||
<dc:Bounds x="478" y="182" width="65" height="27" />
|
||||
</bpmndi:BPMNLabel>
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape id="Event_0r30zqn_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="Event_1csw1p9_di" bpmnElement="LoanAgreementApprovedEndEvent">
|
||||
<dc:Bounds x="772" y="219" width="36" height="36" />
|
||||
<bpmndi:BPMNLabel>
|
||||
<dc:Bounds x="752" y="262" 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:BPMNLabel />
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape id="Activity_1uvc9z4_di" bpmnElement="RejectLoanAgreementServiceTask">
|
||||
<dc:Bounds x="600" y="320" width="100" height="80" />
|
||||
<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="BPMNShape_1ilsqo5" bpmnElement="Participant_0z5fkq5" isHorizontal="true">
|
||||
<dc:Bounds x="500" y="80" width="300" height="60" />
|
||||
<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:BPMNEdge id="Flow_1ax0a41_di" bpmnElement="Flow_1ax0a41">
|
||||
<di:waypoint x="650" y="197" />
|
||||
<di:waypoint x="650" y="140" />
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNEdge id="Flow_1ytdopc_di" bpmnElement="Flow_1ytdopc">
|
||||
<di:waypoint x="650" y="400" />
|
||||
<di:waypoint x="650" y="460" />
|
||||
</bpmndi:BPMNEdge>
|
||||
</bpmndi:BPMNPlane>
|
||||
</bpmndi:BPMNDiagram>
|
||||
</bpmn:definitions>
|
||||
@@ -0,0 +1,115 @@
|
||||
package de.weinbrecht.luc.bpm.architecture;
|
||||
|
||||
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 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 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.common.ProcessConstants.LOAN_AGREEMENT_NUMBER;
|
||||
import static de.weinbrecht.luc.bpm.architecture.common.ProcessConstants.PROCESS_DEFINITION;
|
||||
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)
|
||||
class ProcessTest {
|
||||
|
||||
private static final String START_EVENT = "LoanAgreementReciedStartEvent";
|
||||
private static final String APPROVE_RULE_TASK = "ApproveAgreementRuleTask";
|
||||
private static final String APPROVE_AGREEMENT_SERVICE_TASK = "ApproveLoanAgreementServiceTask";
|
||||
private static final String REJECT_AGREEMENT_SERVICE_TASK = "RejectLoanAgreementServiceTask";
|
||||
|
||||
private static final String DMN_DEFINITION = "approvement-check";
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
registerJavaDelegateMock(ApproveLoanAgreement.class);
|
||||
registerJavaDelegateMock(RejectionLoanAgreement.class);
|
||||
}
|
||||
|
||||
@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();
|
||||
|
||||
assertThat(processInstance).isWaitingAt(START_EVENT);
|
||||
|
||||
execute(job());
|
||||
|
||||
assertThat(processInstance)
|
||||
.hasPassed(START_EVENT,
|
||||
APPROVE_RULE_TASK,
|
||||
APPROVE_AGREEMENT_SERVICE_TASK);
|
||||
|
||||
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);
|
||||
|
||||
verifyJavaDelegateMock(RejectionLoanAgreement.class).executed();
|
||||
|
||||
assertThat(processInstance).isEnded();
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("provideProcessVariablesForDMN")
|
||||
@Deployment(resources = "approve_agreement.dmn")
|
||||
void testTweetApprovalIBM(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)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
package de.weinbrecht.luc.bpm.architecture.loan.agreement;
|
||||
|
||||
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.domain.model.LoanAgreementNumber;
|
||||
import de.weinbrecht.luc.bpm.architecture.loan.agreement.domain.service.LoanAgreementException;
|
||||
import de.weinbrecht.luc.bpm.architecture.loan.agreement.domain.service.LoanAgreementService;
|
||||
import de.weinbrecht.luc.bpm.architecture.loan.agreement.usecase.out.LoanAgreementCommand;
|
||||
import de.weinbrecht.luc.bpm.architecture.loan.agreement.usecase.out.LoanAgreementDistributor;
|
||||
import de.weinbrecht.luc.bpm.architecture.loan.agreement.usecase.out.LoanAgreementQuery;
|
||||
import de.weinbrecht.luc.bpm.architecture.loan.agreement.usecase.out.ProcessEngineCommand;
|
||||
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.domain.model.TestdataGenerator.createLoanAgreementWithNumber;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
@MockitoSettings
|
||||
class LoanAgreementServiceTest {
|
||||
|
||||
@InjectMocks
|
||||
private LoanAgreementService classUnderTest;
|
||||
|
||||
@Mock
|
||||
private LoanAgreementCommand loanAgreementCommand;
|
||||
|
||||
@Mock
|
||||
private ProcessEngineCommand processEngineCommand;
|
||||
|
||||
@Mock
|
||||
private LoanAgreementDistributor loanAgreementDistributor;
|
||||
|
||||
@Mock
|
||||
private LoanAgreementQuery loanAgreementQuery;
|
||||
|
||||
@Test
|
||||
void should_safe_and_set_loan_number_on_creation() {
|
||||
LoanAgreement loanAgreement = createLoanAgreementWithNumber();
|
||||
when(loanAgreementCommand.save(loanAgreement)).thenReturn(loanAgreement.getLoanAgreementNumber());
|
||||
CaseId caseId = new CaseId("12");
|
||||
|
||||
classUnderTest.create(loanAgreement, caseId);
|
||||
|
||||
verify(processEngineCommand).startLoanAgreement(caseId, loanAgreement.getLoanAgreementNumber());
|
||||
}
|
||||
|
||||
@Test
|
||||
void should_catch_exception_and_throw_custom_one_on_creation() {
|
||||
LoanAgreement loanAgreement = createLoanAgreementWithNumber();
|
||||
CaseId caseId = new CaseId("12");
|
||||
when(loanAgreementCommand.save(loanAgreement)).thenThrow(RuntimeException.class);
|
||||
|
||||
assertThrows(LoanAgreementException.class,
|
||||
() -> classUnderTest.create(loanAgreement, caseId));
|
||||
|
||||
verify(processEngineCommand, never()).startLoanAgreement(caseId, loanAgreement.getLoanAgreementNumber());
|
||||
}
|
||||
|
||||
@Test
|
||||
void should_call_distributor_and_set_status_accepted() {
|
||||
LoanAgreementNumber loanAgreementNumber = new LoanAgreementNumber(1L);
|
||||
LoanAgreement loanAgreement = createLoanAgreementWithNumber();
|
||||
when(loanAgreementQuery.loadByNumber(loanAgreementNumber)).thenReturn(loanAgreement);
|
||||
|
||||
classUnderTest.accept(loanAgreementNumber);
|
||||
|
||||
verify(loanAgreementDistributor).sendLoanAgreement(loanAgreement, true);
|
||||
}
|
||||
|
||||
@Test
|
||||
void should_call_distributor_and_set_status_reject() {
|
||||
LoanAgreementNumber loanAgreementNumber = new LoanAgreementNumber(1L);
|
||||
LoanAgreement loanAgreement = createLoanAgreementWithNumber();
|
||||
when(loanAgreementQuery.loadByNumber(loanAgreementNumber)).thenReturn(loanAgreement);
|
||||
|
||||
classUnderTest.reject(loanAgreementNumber);
|
||||
|
||||
verify(loanAgreementDistributor).sendLoanAgreement(loanAgreement, false);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
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.common.ProcessConstants.LOAN_AGREEMENT_NUMBER;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
@MockitoSettings
|
||||
class ApproveLoanAgreementTest {
|
||||
@InjectMocks
|
||||
private ApproveLoanAgreement classUnderTest;
|
||||
|
||||
@Mock
|
||||
private LoanAgreementStatusCommand loanAgreementStatusCommand;
|
||||
|
||||
@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);
|
||||
|
||||
verify(loanAgreementStatusCommand).accept(loanAgreementNumber);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
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.common.ProcessConstants.LOAN_AGREEMENT_NUMBER;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
@MockitoSettings
|
||||
class RejectionLoanAgreementTest {
|
||||
|
||||
@InjectMocks
|
||||
private RejectionLoanAgreement classUnderTest;
|
||||
|
||||
@Mock
|
||||
private LoanAgreementStatusCommand loanAgreementStatusCommand;
|
||||
|
||||
@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);
|
||||
|
||||
verify(loanAgreementStatusCommand).reject(loanAgreementNumber);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
package de.weinbrecht.luc.bpm.architecture.loan.agreement.adapter.in.web;
|
||||
|
||||
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.usecase.in.LoanAgreementCreation;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
|
||||
import org.springframework.boot.test.mock.mockito.MockBean;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.test.web.servlet.MockMvc;
|
||||
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
||||
import static org.springframework.util.MimeTypeUtils.APPLICATION_JSON_VALUE;
|
||||
|
||||
@WebMvcTest(LoanAgreementController.class)
|
||||
@Import(LoanAgreementMapper.class)
|
||||
class LoanAgreementControllerTest {
|
||||
|
||||
@Autowired
|
||||
private MockMvc mockMvc;
|
||||
|
||||
@MockBean
|
||||
private LoanAgreementCreation loanAgreementCreation;
|
||||
|
||||
@Test
|
||||
void should_class_creation_on_post() throws Exception {
|
||||
String requestJson = "{\"customerNumber\": \"A1\",\"name\": \"Tester\",\"mailAddress\": \"tester@web.io\",\"amount\": 1100}";
|
||||
|
||||
mockMvc.perform(
|
||||
post("/loan/agreement/1")
|
||||
.contentType(APPLICATION_JSON_VALUE)
|
||||
.content(requestJson)
|
||||
)
|
||||
.andDo(print())
|
||||
.andExpect(status().isOk());
|
||||
verify(loanAgreementCreation).create(
|
||||
new LoanAgreement(
|
||||
new Recipient(
|
||||
new CustomerNumber("A1"),
|
||||
new Name("Tester"),
|
||||
new MailAddress("tester@web.io")
|
||||
),
|
||||
new Amount(1100)
|
||||
),
|
||||
new CaseId("1"));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package de.weinbrecht.luc.bpm.architecture.loan.agreement.adapter.in.web;
|
||||
|
||||
import de.weinbrecht.luc.bpm.architecture.loan.agreement.domain.model.LoanAgreement;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
class LoanAgreementMapperTest {
|
||||
|
||||
private LoanAgreementMapper classUnderTest = new LoanAgreementMapper();
|
||||
|
||||
@Test
|
||||
void should_map_all_fields() {
|
||||
LoanAgreementResource loanAgreementResource = createLoanAgreementResource();
|
||||
|
||||
LoanAgreement result = classUnderTest.mapToDomain(loanAgreementResource);
|
||||
|
||||
assertThat(result).isNotNull();
|
||||
assertThat(result.getAmount().getValue()).isEqualTo(loanAgreementResource.getAmount());
|
||||
assertThat(result.getRecipient().getName().getValue()).isEqualTo(loanAgreementResource.getName());
|
||||
assertThat(result.getRecipient().getMailAddress().getValue()).isEqualTo(loanAgreementResource.getMailAddress());
|
||||
assertThat(result.getRecipient().getCustomerNumber().getValue()).isEqualTo(loanAgreementResource.getCustomerNumber());
|
||||
}
|
||||
|
||||
private LoanAgreementResource createLoanAgreementResource() {
|
||||
LoanAgreementResource loanAgreementResource = new LoanAgreementResource();
|
||||
loanAgreementResource.setAmount(400);
|
||||
loanAgreementResource.setCustomerNumber("A11");
|
||||
loanAgreementResource.setMailAddress("tester@web.io");
|
||||
loanAgreementResource.setName("Tester");
|
||||
return loanAgreementResource;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package de.weinbrecht.luc.bpm.architecture.loan.agreement.adapter.out.db;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
@DataJpaTest
|
||||
class LoanAgreementCRUDRepositoryTest {
|
||||
|
||||
@Autowired
|
||||
private LoanAgreementCRUDRepository classUnderTest;
|
||||
|
||||
@Test
|
||||
void should_safe_entity() {
|
||||
LoanAgreementEntity result = classUnderTest.save(new LoanAgreementEntity());
|
||||
|
||||
assertThat(result).isNotNull();
|
||||
assertThat(result.getId()).isNotNull();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package de.weinbrecht.luc.bpm.architecture.loan.agreement.adapter.out.db;
|
||||
|
||||
import de.weinbrecht.luc.bpm.architecture.loan.agreement.domain.model.LoanAgreement;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static de.weinbrecht.luc.bpm.architecture.loan.agreement.adapter.out.db.TestDataGenerator.createLoanAgreementEntity;
|
||||
import static de.weinbrecht.luc.bpm.architecture.loan.agreement.domain.model.TestdataGenerator.createLoanAgreement;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
class LoanAgreementMapperTest {
|
||||
|
||||
private LoanAgreementMapper classUnderTest = new LoanAgreementMapper();
|
||||
|
||||
@Test
|
||||
void should_map_all_fields_to_db() {
|
||||
LoanAgreement loanAgreement = createLoanAgreement();
|
||||
|
||||
LoanAgreementEntity result = classUnderTest.mapToDb(loanAgreement);
|
||||
|
||||
assertThat(result.getAmount()).isEqualTo(loanAgreement.getAmount().getValue());
|
||||
assertThat(result.getName()).isEqualTo(loanAgreement.getRecipient().getName().getValue());
|
||||
assertThat(result.getMailAddress()).isEqualTo(loanAgreement.getRecipient().getMailAddress().getValue());
|
||||
assertThat(result.getCustomerNumber()).isEqualTo(loanAgreement.getRecipient().getCustomerNumber().getValue());
|
||||
}
|
||||
|
||||
@Test
|
||||
void should_map_all_fields_to_domain() {
|
||||
LoanAgreementEntity loanAgreementEntity = createLoanAgreementEntity();
|
||||
|
||||
LoanAgreement result = classUnderTest.mapToDomain(loanAgreementEntity);
|
||||
|
||||
assertThat(result.getLoanAgreementNumber().getValue()).isEqualTo(loanAgreementEntity.getId());
|
||||
assertThat(result.getAmount().getValue()).isEqualTo(loanAgreementEntity.getAmount());
|
||||
assertThat(result.getRecipient().getName().getValue()).isEqualTo(loanAgreementEntity.getName());
|
||||
assertThat(result.getRecipient().getMailAddress().getValue()).isEqualTo(loanAgreementEntity.getMailAddress());
|
||||
assertThat(result.getRecipient().getCustomerNumber().getValue()).isEqualTo(loanAgreementEntity.getCustomerNumber());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
package de.weinbrecht.luc.bpm.architecture.loan.agreement.adapter.out.db;
|
||||
|
||||
import de.weinbrecht.luc.bpm.architecture.loan.agreement.domain.model.LoanAgreement;
|
||||
import de.weinbrecht.luc.bpm.architecture.loan.agreement.domain.model.LoanAgreementNumber;
|
||||
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.out.db.TestDataGenerator.createLoanAgreementEntity;
|
||||
import static de.weinbrecht.luc.bpm.architecture.loan.agreement.domain.model.TestdataGenerator.createLoanAgreement;
|
||||
import static de.weinbrecht.luc.bpm.architecture.loan.agreement.domain.model.TestdataGenerator.createLoanAgreementWithNumber;
|
||||
import static java.util.Optional.empty;
|
||||
import static java.util.Optional.of;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.mockito.Answers.CALLS_REAL_METHODS;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@MockitoSettings
|
||||
class LoanAgreementRepositoryTest {
|
||||
|
||||
@InjectMocks
|
||||
private LoanAgreementRepository classUnderTest;
|
||||
|
||||
@Mock
|
||||
private LoanAgreementCRUDRepository crudRepository;
|
||||
|
||||
@Mock(answer = CALLS_REAL_METHODS)
|
||||
private LoanAgreementMapper mapper;
|
||||
|
||||
@Test
|
||||
void should_call_crud_repo_and_safe() {
|
||||
LoanAgreement loanAgreement = createLoanAgreement();
|
||||
LoanAgreementEntity dbEntity = createLoanAgreementEntity();
|
||||
when(crudRepository.save(mapper.mapToDb(loanAgreement))).thenReturn(dbEntity);
|
||||
|
||||
LoanAgreementNumber result = classUnderTest.save(loanAgreement);
|
||||
|
||||
assertThat(result.getValue()).isEqualTo(dbEntity.getId());
|
||||
}
|
||||
|
||||
@Test
|
||||
void should_call_crud_repo_and_find_by_id() {
|
||||
LoanAgreement loanAgreement = createLoanAgreementWithNumber();
|
||||
when(crudRepository.findById(loanAgreement.getLoanAgreementNumber().getValue()))
|
||||
.thenReturn(of(createLoanAgreementEntity()));
|
||||
|
||||
LoanAgreement result = classUnderTest.loadByNumber(loanAgreement.getLoanAgreementNumber());
|
||||
|
||||
assertThat(result).isEqualTo(loanAgreement);
|
||||
}
|
||||
|
||||
@Test
|
||||
void should_call_crud_repo_and_find_by_id_throw_custom_exception_if_not_found() {
|
||||
LoanAgreement loanAgreement = createLoanAgreementWithNumber();
|
||||
when(crudRepository.findById(loanAgreement.getLoanAgreementNumber().getValue())).thenReturn(empty());
|
||||
|
||||
assertThrows(LoanAgreementNotFoundException.class,
|
||||
() -> classUnderTest.loadByNumber(loanAgreement.getLoanAgreementNumber()));
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package de.weinbrecht.luc.bpm.architecture.loan.agreement.adapter.out.db;
|
||||
|
||||
import de.weinbrecht.luc.bpm.architecture.loan.agreement.domain.model.LoanAgreement;
|
||||
|
||||
import static de.weinbrecht.luc.bpm.architecture.loan.agreement.domain.model.TestdataGenerator.createLoanAgreementWithNumber;
|
||||
|
||||
class TestDataGenerator {
|
||||
public static LoanAgreementEntity createLoanAgreementEntity() {
|
||||
LoanAgreement loanAgreement = createLoanAgreementWithNumber();
|
||||
LoanAgreementEntity loanAgreementEntity = new LoanAgreementEntity();
|
||||
loanAgreementEntity.setId(loanAgreement.getLoanAgreementNumber().getValue());
|
||||
loanAgreementEntity.setAmount(loanAgreement.getAmount().getValue());
|
||||
loanAgreementEntity.setName(loanAgreement.getRecipient().getName().getValue());
|
||||
loanAgreementEntity.setCustomerNumber(loanAgreement.getRecipient().getCustomerNumber().getValue());
|
||||
loanAgreementEntity.setMailAddress(loanAgreement.getRecipient().getMailAddress().getValue());
|
||||
return loanAgreementEntity;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
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 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.common.ProcessConstants.LOAN_AGREEMENT_NUMBER;
|
||||
import static de.weinbrecht.luc.bpm.architecture.common.ProcessConstants.START_EVENT_MESSAGE_REF;
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
||||
@MockitoSettings
|
||||
class ProcessEngineClientTest {
|
||||
|
||||
@InjectMocks
|
||||
private ProcessEngineClient classUnderTest;
|
||||
|
||||
@Mock
|
||||
private RuntimeService runtimeService;
|
||||
|
||||
private final CaseId caseId = new CaseId("11");
|
||||
private final LoanAgreementNumber loanAgreementNumber = new LoanAgreementNumber(1L);
|
||||
|
||||
@Test
|
||||
void should_class_runtime_service_to_start() {
|
||||
Map<String, Object> processVariables = new HashMap<>();
|
||||
processVariables.put(LOAN_AGREEMENT_NUMBER, loanAgreementNumber.getValue());
|
||||
|
||||
classUnderTest.startLoanAgreement(caseId, loanAgreementNumber);
|
||||
|
||||
verify(runtimeService).startProcessInstanceByMessage(START_EVENT_MESSAGE_REF, caseId.getValue(), processVariables);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
package de.weinbrecht.luc.bpm.architecture.loan.agreement.domain.model;
|
||||
|
||||
import io.github.domainprimitives.validation.InvariantException;
|
||||
import org.junit.jupiter.api.Nested;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static de.weinbrecht.luc.bpm.architecture.loan.agreement.domain.model.TestdataGenerator.createRecipient;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
class LoanAgreementTest {
|
||||
|
||||
@Test
|
||||
void should_create_valid_object() {
|
||||
Recipient recipient = createRecipient();
|
||||
Amount amount = new Amount(300);
|
||||
|
||||
LoanAgreement loanAgreement = new LoanAgreement(recipient, amount);
|
||||
|
||||
assertThat(loanAgreement).isNotNull();
|
||||
assertThat(loanAgreement.getRecipient()).isEqualTo(recipient);
|
||||
assertThat(loanAgreement.getAmount()).isEqualTo(amount);
|
||||
}
|
||||
|
||||
@Test
|
||||
void should_create_valid_object_with_numer() {
|
||||
Recipient recipient = createRecipient();
|
||||
Amount amount = new Amount(300);
|
||||
LoanAgreementNumber loanAgreementNumber = new LoanAgreementNumber(1L);
|
||||
|
||||
LoanAgreement loanAgreement = new LoanAgreement(loanAgreementNumber, recipient, amount);
|
||||
|
||||
assertThat(loanAgreement).isNotNull();
|
||||
assertThat(loanAgreement.getRecipient()).isEqualTo(recipient);
|
||||
assertThat(loanAgreement.getAmount()).isEqualTo(amount);
|
||||
assertThat(loanAgreement.getLoanAgreementNumber()).isEqualTo(loanAgreementNumber);
|
||||
}
|
||||
|
||||
@Test
|
||||
void should_create_valid_object_with_number() {
|
||||
Recipient recipient = createRecipient();
|
||||
Amount amount = new Amount(300);
|
||||
LoanAgreementNumber loanAgreementNumber = new LoanAgreementNumber(1L);
|
||||
|
||||
LoanAgreement loanAgreement = new LoanAgreement(loanAgreementNumber, recipient, amount);
|
||||
|
||||
assertNotNull(loanAgreement);
|
||||
assertEquals(loanAgreement.getLoanAgreementNumber(), loanAgreementNumber);
|
||||
assertEquals(loanAgreement.getRecipient(), recipient);
|
||||
assertEquals(loanAgreement.getAmount(), amount);
|
||||
}
|
||||
|
||||
@Nested
|
||||
class InvariantTest {
|
||||
@Test
|
||||
void should_throw_invariant_exception_if_recipient_is_null() {
|
||||
Amount amount = new Amount(300);
|
||||
assertThrows(InvariantException.class, () -> new LoanAgreement(null, amount));
|
||||
}
|
||||
|
||||
@Test
|
||||
void should_throw_invariant_exception_if_amount_is_null() {
|
||||
assertThrows(InvariantException.class, () -> new LoanAgreement(createRecipient(), null));
|
||||
}
|
||||
|
||||
@Test
|
||||
void should_throw_invariant_exception_if_number_is_null() {
|
||||
Amount amount = new Amount(300);
|
||||
assertThrows(InvariantException.class, () -> new LoanAgreement(null, createRecipient(), amount));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
package 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 io.github.domainprimitives.validation.InvariantException;
|
||||
import org.junit.jupiter.api.Nested;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
|
||||
class RecipientTest {
|
||||
|
||||
@Test
|
||||
void should_create_valid_object() {
|
||||
Name name = new Name("Tester");
|
||||
MailAddress mailAddress = new MailAddress("tester@web.io");
|
||||
CustomerNumber customerNumber = new CustomerNumber("A-1");
|
||||
|
||||
Recipient recipient = new Recipient(customerNumber, name, mailAddress);
|
||||
|
||||
assertThat(recipient).isNotNull();
|
||||
assertThat(recipient.getCustomerNumber()).isEqualTo(customerNumber);
|
||||
assertThat(recipient.getName()).isEqualTo(name);
|
||||
assertThat(recipient.getMailAddress()).isEqualTo(mailAddress);
|
||||
}
|
||||
|
||||
@Nested
|
||||
class InvariantTest {
|
||||
@Test
|
||||
void should_throw_invariant_exception_if_customer_number_is_null() {
|
||||
Name name = new Name("Tester");
|
||||
MailAddress mailAddress = new MailAddress("tester@web.io");
|
||||
|
||||
assertThrows(InvariantException.class, () -> new Recipient(null, name, mailAddress));
|
||||
}
|
||||
|
||||
@Test
|
||||
void should_throw_invariant_exception_if_name_is_null() {
|
||||
MailAddress mailAddress = new MailAddress("tester@web.io");
|
||||
CustomerNumber customerNumber = new CustomerNumber("A-1");
|
||||
|
||||
assertThrows(InvariantException.class, () -> new Recipient(customerNumber, null, mailAddress));
|
||||
}
|
||||
|
||||
@Test
|
||||
void should_throw_invariant_exception_if_mail_is_null() {
|
||||
Name name = new Name("Tester");
|
||||
CustomerNumber customerNumber = new CustomerNumber("A-1");
|
||||
|
||||
assertThrows(InvariantException.class, () -> new Recipient(customerNumber, name, null));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package 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;
|
||||
|
||||
public class TestdataGenerator {
|
||||
|
||||
public static Recipient createRecipient() {
|
||||
return new Recipient(new CustomerNumber("A-1"), new Name("Tester"), new MailAddress("tester@newweb.io"));
|
||||
}
|
||||
|
||||
public static LoanAgreement createLoanAgreement() {
|
||||
return new LoanAgreement(
|
||||
new LoanAgreementNumber(1L),
|
||||
new Recipient(
|
||||
new CustomerNumber("A-1"),
|
||||
new Name("Tester"),
|
||||
new MailAddress("tester@web.de")
|
||||
),
|
||||
new Amount(400)
|
||||
);
|
||||
}
|
||||
|
||||
public static LoanAgreement createLoanAgreementWithNumber() {
|
||||
return new LoanAgreement(
|
||||
new LoanAgreementNumber(1L),
|
||||
new Recipient(
|
||||
new CustomerNumber("A-1"),
|
||||
new Name("Tester"),
|
||||
new MailAddress("tester@web.de")
|
||||
),
|
||||
new Amount(400)
|
||||
);
|
||||
}
|
||||
}
|
||||
5
src/test/resources/application.yaml
Normal file
5
src/test/resources/application.yaml
Normal file
@@ -0,0 +1,5 @@
|
||||
spring.datasource.url: jdbc:h2:file:./camunda-h2-database
|
||||
|
||||
camunda.bpm.admin-user:
|
||||
id: admin
|
||||
password: pw
|
||||
22
src/test/resources/camunda.cfg.xml
Normal file
22
src/test/resources/camunda.cfg.xml
Normal file
@@ -0,0 +1,22 @@
|
||||
<?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>
|
||||
63
src/test/resources/logback-test.xml
Normal file
63
src/test/resources/logback-test.xml
Normal file
@@ -0,0 +1,63 @@
|
||||
<configuration>
|
||||
|
||||
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
|
||||
<!-- encoders are assigned the type
|
||||
ch.qos.logback.classic.encoder.PatternLayoutEncoder by default -->
|
||||
<encoder>
|
||||
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
<logger name="org.apache.ibatis" level="info" />
|
||||
<!--
|
||||
<logger name="org.apache.ibatis" level="DEBUG"/>
|
||||
-->
|
||||
|
||||
<logger name="javax.activation" level="info" />
|
||||
|
||||
<logger name="org.springframework" level="info" />
|
||||
|
||||
<logger name="org.camunda" level="info" />
|
||||
<!--
|
||||
<logger name="org.camunda" level="DEBUG"/>
|
||||
-->
|
||||
|
||||
<logger name="org.camunda.bpm.engine.test" level="debug" />
|
||||
|
||||
<!--
|
||||
<logger name="org.camunda.bpm.engine.bpmn.parser" level="debug" />
|
||||
<logger name="org.camunda.bpm.engine.bpmn.behavior" level="debug" />
|
||||
|
||||
<logger name="org.camunda.bpm.engine.cmmn.transformer" level="debug" />
|
||||
<logger name="org.camunda.bpm.engine.cmmn.behavior" level="debug" />
|
||||
<logger name="org.camunda.bpm.engine.cmmn.operation" level="debug" />
|
||||
|
||||
<logger name="org.camunda.bpm.engine.cmd" level="debug" />
|
||||
<logger name="org.camunda.bpm.engine.persistence" level="debug" />
|
||||
<logger name="org.camunda.bpm.engine.impl.persistence.entity" level="debug" />
|
||||
<logger name="org.camunda.bpm.engine.impl.history.event" level="debug" />
|
||||
<logger name="org.camunda.bpm.engine.impl.batch.history" level="debug" />
|
||||
<logger name="org.camunda.bpm.engine.impl.batch" level="debug" />
|
||||
<logger name="org.camunda.bpm.engine.history" level="debug" />
|
||||
<logger name="org.camunda.bpm.engine.impl.cmmn.entity.repository" level="debug" />
|
||||
<logger name="org.camunda.bpm.engine.impl.cmmn.entity.runtime" level="debug" />
|
||||
<logger name="org.camunda.bpm.engine.impl.dmn.entity.repository" level="debug" />
|
||||
<logger name="org.camunda.bpm.engine.tx" level="debug" />
|
||||
<logger name="org.camunda.bpm.engine.cfg" level="debug" />
|
||||
<logger name="org.camunda.bpm.engine.jobexecutor" level="debug" />
|
||||
<logger name="org.camunda.bpm.engine.context" level="debug" />
|
||||
<logger name="org.camunda.bpm.engine.core" level="debug" />
|
||||
<logger name="org.camunda.bpm.engine.pvm" level="debug" />
|
||||
|
||||
<logger name="org.camunda.bpm.engine.metrics" level="debug" />
|
||||
<logger name="org.camunda.bpm.engine.util" level="debug" />
|
||||
|
||||
<logger name="org.camunda.bpm.application" level="debug" />
|
||||
<logger name="org.camunda.bpm.container" level="debug" />
|
||||
-->
|
||||
|
||||
<root level="debug">
|
||||
<appender-ref ref="STDOUT" />
|
||||
</root>
|
||||
|
||||
</configuration>
|
||||
Reference in New Issue
Block a user