initial commit
28
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
# 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
|
||||
|
||||
# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
|
||||
hs_err_pid*
|
||||
|
||||
.idea/
|
||||
target/
|
||||
*.iml
|
||||
.DS_Store
|
||||
21
LICENSE
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2018 Nikola Kasev
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
2
README.md
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
# workshop-baker
|
||||
A workshop for Java developers to get started with Baker and develop an intuition for implementing complex orchestration logic in APIs. Start [here](docs/baker-workshop.pdf).
|
||||
207
docs/baker-workshop.md
Normal file
|
|
@ -0,0 +1,207 @@
|
|||

|
||||
|
||||
---
|
||||
|
||||
# Workshop Agenda
|
||||
## Why Baker? (10 minutes)
|
||||
## Develop an Intuition for Baker (15 m.)
|
||||
## Implement a Web Shop Flow (25 m.)
|
||||
|
||||
---
|
||||
|
||||
# Why Baker?
|
||||
|
||||
---
|
||||
|
||||
## Our Challenge
|
||||
### Interact with 12 Different Systems (complex environment)
|
||||
### A Flow of 27 Steps (difficult to reason about the logic)
|
||||
### From 2 minutes to 6 hours (stateful...)
|
||||
|
||||
---
|
||||
|
||||
## Requirements
|
||||
### TIBCO is contained
|
||||
### Java developers
|
||||
### State in two data-centres
|
||||
### Systems fail all the time
|
||||
|
||||
---
|
||||
|
||||
| Current Account | Savings Account | Customer Onboarding |
|
||||
| :--- | :---: | ---: |
|
||||
| Verify Identity | Verify Identity | Verify Identity |
|
||||
| Register Individual | Register Individual | Register Individual |
|
||||
| Open *Current* Account | Open *Savings* Account | `n/a` |
|
||||
| Issue Debit Card | `n/a` | `n/a` |
|
||||
| Send Message | Send Message | Send Message |
|
||||
| Register Ownership | Register Ownership | `n/a` |
|
||||
|
||||
---
|
||||
|
||||

|
||||
|
||||
---
|
||||
## Baker is a Scala Library
|
||||
### Declare the Logic Like a Recipe
|
||||
### Visualize the Logic
|
||||
### Don't Worry About Retries and State
|
||||
### Re-use What's Already There
|
||||
|
||||
---
|
||||
|
||||

|
||||
|
||||
# Recipes
|
||||
## Events
|
||||
## Ingredients
|
||||
## Interactions
|
||||
|
||||
---
|
||||

|
||||
# What is an Event?
|
||||
## It's what happens
|
||||
## Sensory and System Events
|
||||
|
||||
---
|
||||

|
||||
# What is an Ingredient?
|
||||
## It's a container for data
|
||||
## Can be a primitive type or a POJO
|
||||
|
||||
---
|
||||

|
||||
# What is an Interaction?
|
||||
## It's where you put your system calls
|
||||
## Capability, not a technical call
|
||||
|
||||
---
|
||||
# Hands-On
|
||||
## Setup Environment
|
||||
## Explain the Web-shop Flow
|
||||
## Exercise, Make Unit-tests Green
|
||||
|
||||
---
|
||||
## Setup Environment
|
||||
- clone https://github.com/nikolakasev/workshop-baker
|
||||
- mvn clean test
|
||||
- tests fail on purpose
|
||||
- do the [exercises](#exercises) to get them green
|
||||
|
||||
---
|
||||
[.background-color: #FFFFFF]
|
||||

|
||||
|
||||
---
|
||||
[.background-color: #FFFFFF]
|
||||

|
||||
|
||||
---
|
||||
[.background-color: #FFFFFF]
|
||||

|
||||
|
||||
---
|
||||
[.background-color: #FFFFFF]
|
||||

|
||||
|
||||
---
|
||||
[.background-color: #FFFFFF]
|
||||

|
||||
|
||||
---
|
||||
<a name="exercises"/>
|
||||
|
||||
## Exercises
|
||||
1. Put all interactions in the recipe
|
||||
2. Include sensory events in the recipe, use withSensoryEvents()
|
||||
3. Add extra conditions when an interaction must be executed, use withRequiredEvent()
|
||||
4. Change the customerInfo class from String to a POJO
|
||||
|
||||
---
|
||||
[.background-color: #FFFFFF]
|
||||

|
||||
|
||||
# 5. Call the Postoffice and Accountant Systems in parallel
|
||||
|
||||
---
|
||||
|
||||
# What Can Go Wrong?
|
||||
|
||||
---
|
||||
[.background-color: #FFFFFF]
|
||||

|
||||
|
||||
# Recipe has no sensory input, Baker doesn't know where the data comes from
|
||||
|
||||
---
|
||||
[.background-color: #FFFFFF]
|
||||

|
||||
|
||||
# Which two interactions will be executed when the OrderPlaced event occurs? Is this the desired behavior?
|
||||
|
||||
---
|
||||
# Desired Recipe
|
||||
|
||||
---
|
||||
[.background-color: #FFFFFF]
|
||||

|
||||
|
||||
---
|
||||
# Food for Thought
|
||||
- What to do when the order is invalid?
|
||||
- Baker helps developers think about rainy-day scenarios
|
||||
- What if systems fails, what is desired?
|
||||
- See withFailureStrategy(), withDefaultFailureStrategy() methods and InteractionFailureStrategy class
|
||||
|
||||
---
|
||||
# Food for Thought / 2
|
||||
- How to agree on functionality with stakeholders?
|
||||
- Baker represents the Java logic visually
|
||||
- How to change existing functionality? How to be confident nothing breaks?
|
||||
- Baker validates the recipe and reports inconsistencies (such as missing ingredients)
|
||||
|
||||
---
|
||||
# Good to Know
|
||||
|
||||
---
|
||||
|
||||
## Short-lived vs. long-running flows
|
||||
|
||||
---
|
||||
|
||||
## State is taken care of:
|
||||
|
||||
- Cassandra for persistent storage
|
||||
- Ingredients encrypted by default
|
||||
- State recovered automatically
|
||||
- Configurable Time-to-live
|
||||
|
||||
---
|
||||
|
||||
## When failure occurs:
|
||||
|
||||
- Baker retries technical failures with exponential backoff
|
||||
- Any Java Exception thrown is a technical failure
|
||||
- Retrying works well with **idempotent** services
|
||||
- Deal with functional failure in your recipe
|
||||
|
||||
---
|
||||
|
||||
## Baker Catalogue: Some Stats
|
||||
### Internal ING library
|
||||
### 80+ Re-usable Interactions
|
||||
### 10+ Teams Using Baker
|
||||
|
||||
---
|
||||
|
||||
## Baker vs. PEGA
|
||||
|
||||
| Baker | PEGA |
|
||||
| :--- | :---: |
|
||||
| Computer | Computer + Human |
|
||||
| Focused on APIs | Focused on processes |
|
||||
| No License Costs | License Costs |
|
||||
| No Usage Costs | Pay per Case Costs |
|
||||
| For Java Developers | For PEGA Specialists |
|
||||
| From ING | From PEGA |
|
||||
|
||||
BIN
docs/baker-workshop.pdf
Normal file
BIN
docs/baker.png
Normal file
|
After Width: | Height: | Size: 49 KiB |
BIN
docs/desired-end-result.png
Normal file
|
After Width: | Height: | Size: 157 KiB |
BIN
docs/donuts.jpg
Normal file
|
After Width: | Height: | Size: 3 MiB |
BIN
docs/incorrect-sequence.png
Normal file
|
After Width: | Height: | Size: 117 KiB |
BIN
docs/ingredient.png
Normal file
|
After Width: | Height: | Size: 74 KiB |
BIN
docs/interaction.png
Normal file
|
After Width: | Height: | Size: 70 KiB |
BIN
docs/manufacture-goods.png
Normal file
|
After Width: | Height: | Size: 24 KiB |
67
docs/manufacture-goods.svg
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
<svg width="516pt" height="584pt"
|
||||
viewBox="0.00 0.00 516.23 584.47" xmlns="http://www.w3.org/2000/svg">
|
||||
<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(14.4 570.0673)">
|
||||
<title>%3</title>
|
||||
<polygon fill="#ffffff" stroke="transparent"
|
||||
points="-14.4,14.4 -14.4,-570.0673 501.832,-570.0673 501.832,14.4 -14.4,14.4"/>
|
||||
<!-- GoodsManufactured -->
|
||||
<g id="node1" class="node">
|
||||
<title>GoodsManufactured</title>
|
||||
<path fill="#767676" stroke="#767676"
|
||||
d="M232.207,-283.9674C232.207,-283.9674 11.2928,-218.7508 11.2928,-218.7508 5.5383,-217.052 5.5383,-213.6544 11.2928,-211.9556 11.2928,-211.9556 232.207,-146.739 232.207,-146.739 237.9615,-145.0402 249.4705,-145.0402 255.225,-146.739 255.225,-146.739 476.1392,-211.9556 476.1392,-211.9556 481.8937,-213.6544 481.8937,-217.052 476.1392,-218.7508 476.1392,-218.7508 255.225,-283.9674 255.225,-283.9674 249.4705,-285.6662 237.9615,-285.6662 232.207,-283.9674"/>
|
||||
<text text-anchor="middle" x="243.716" y="-211.7561" font-family="ING Me" font-size="22.00" fill="#ffffff">
|
||||
GoodsManufactured
|
||||
</text>
|
||||
</g>
|
||||
<!-- goods -->
|
||||
<g id="node2" class="node">
|
||||
<title>goods</title>
|
||||
<ellipse fill="#ff6200" stroke="#ff6200" cx="243.716" cy="-53.6736" rx="53.8479" ry="53.8479"/>
|
||||
<text text-anchor="middle" x="243.716" y="-50.0766" font-family="ING Me" font-size="22.00" fill="#ffffff">
|
||||
goods
|
||||
</text>
|
||||
</g>
|
||||
<!-- GoodsManufactured->goods -->
|
||||
<g id="edge1" class="edge">
|
||||
<title>GoodsManufactured->goods</title>
|
||||
<path fill="none" stroke="#000000"
|
||||
d="M243.716,-143.0254C243.716,-134.5375 243.716,-125.9229 243.716,-117.5618"/>
|
||||
<polygon fill="#000000" stroke="#000000"
|
||||
points="247.2161,-117.5141 243.716,-107.5141 240.2161,-117.5141 247.2161,-117.5141"/>
|
||||
</g>
|
||||
<!-- order -->
|
||||
<g id="node3" class="node">
|
||||
<title>order</title>
|
||||
<ellipse fill="#ee0000" stroke="#ee0000" stroke-width="5" cx="243.716" cy="-507.5161" rx="48.3028"
|
||||
ry="48.3028"/>
|
||||
<text text-anchor="middle" x="243.716" y="-503.9191" font-family="ING Me" font-size="22.00" fill="#ffffff">
|
||||
order
|
||||
</text>
|
||||
</g>
|
||||
<!-- ManufactureGoods -->
|
||||
<g id="node4" class="node">
|
||||
<title>ManufactureGoods</title>
|
||||
<path fill="#525199" stroke="#525199" stroke-width="2"
|
||||
d="M361.282,-423.3679C361.282,-423.3679 126.15,-423.3679 126.15,-423.3679 120.15,-423.3679 114.15,-417.3679 114.15,-411.3679 114.15,-411.3679 114.15,-335.3561 114.15,-335.3561 114.15,-329.3561 120.15,-323.3561 126.15,-323.3561 126.15,-323.3561 361.282,-323.3561 361.282,-323.3561 367.282,-323.3561 373.282,-329.3561 373.282,-335.3561 373.282,-335.3561 373.282,-411.3679 373.282,-411.3679 373.282,-417.3679 367.282,-423.3679 361.282,-423.3679"/>
|
||||
<text text-anchor="middle" x="243.716" y="-369.765" font-family="ING Me" font-size="22.00" fill="#ffffff">
|
||||
ManufactureGoods
|
||||
</text>
|
||||
</g>
|
||||
<!-- order->ManufactureGoods -->
|
||||
<g id="edge2" class="edge">
|
||||
<title>order->ManufactureGoods</title>
|
||||
<path fill="none" stroke="#000000"
|
||||
d="M243.716,-459.0834C243.716,-450.8663 243.716,-442.2438 243.716,-433.7649"/>
|
||||
<polygon fill="#000000" stroke="#000000"
|
||||
points="247.2161,-433.5306 243.716,-423.5307 240.2161,-433.5307 247.2161,-433.5306"/>
|
||||
</g>
|
||||
<!-- ManufactureGoods->GoodsManufactured -->
|
||||
<g id="edge3" class="edge">
|
||||
<title>ManufactureGoods->GoodsManufactured</title>
|
||||
<path fill="none" stroke="#000000"
|
||||
d="M243.716,-322.9326C243.716,-314.8182 243.716,-306.2389 243.716,-297.6079"/>
|
||||
<polygon fill="#000000" stroke="#000000"
|
||||
points="247.2161,-297.4767 243.716,-287.4768 240.2161,-297.4768 247.2161,-297.4767"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 4.1 KiB |
BIN
docs/no-sensory-input.png
Normal file
|
After Width: | Height: | Size: 84 KiB |
BIN
docs/send-invoice.png
Normal file
|
After Width: | Height: | Size: 18 KiB |
51
docs/send-invoice.svg
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
<svg width="341pt" height="553pt"
|
||||
viewBox="0.00 0.00 341.46 553.44" xmlns="http://www.w3.org/2000/svg">
|
||||
<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(14.4 539.0368)">
|
||||
<title>%3</title>
|
||||
<polygon fill="#ffffff" stroke="transparent"
|
||||
points="-14.4,14.4 -14.4,-539.0368 327.064,-539.0368 327.064,14.4 -14.4,14.4"/>
|
||||
<!-- InvoiceSent -->
|
||||
<g id="node1" class="node">
|
||||
<title>InvoiceSent</title>
|
||||
<path fill="#767676" stroke="#767676"
|
||||
d="M145.4348,-138.9927C145.4348,-138.9927 11.065,-77.0309 11.065,-77.0309 5.6164,-74.5184 5.6164,-69.4934 11.065,-66.9809 11.065,-66.9809 145.4348,-5.0191 145.4348,-5.0191 150.8834,-2.5066 161.7806,-2.5066 167.2292,-5.0191 167.2292,-5.0191 301.599,-66.9809 301.599,-66.9809 307.0476,-69.4934 307.0476,-74.5184 301.599,-77.0309 301.599,-77.0309 167.2292,-138.9927 167.2292,-138.9927 161.7806,-141.5052 150.8834,-141.5052 145.4348,-138.9927"/>
|
||||
<text text-anchor="middle" x="156.332" y="-68.4088" font-family="ING Me" font-size="22.00" fill="#ffffff">
|
||||
InvoiceSent
|
||||
</text>
|
||||
</g>
|
||||
<!-- SendInvoice -->
|
||||
<g id="node2" class="node">
|
||||
<title>SendInvoice</title>
|
||||
<path fill="#525199" stroke="#525199" stroke-width="2"
|
||||
d="M238.784,-280.0206C238.784,-280.0206 73.88,-280.0206 73.88,-280.0206 67.88,-280.0206 61.88,-274.0206 61.88,-268.0206 61.88,-268.0206 61.88,-192.0088 61.88,-192.0088 61.88,-186.0088 67.88,-180.0088 73.88,-180.0088 73.88,-180.0088 238.784,-180.0088 238.784,-180.0088 244.784,-180.0088 250.784,-186.0088 250.784,-192.0088 250.784,-192.0088 250.784,-268.0206 250.784,-268.0206 250.784,-274.0206 244.784,-280.0206 238.784,-280.0206"/>
|
||||
<text text-anchor="middle" x="156.332" y="-226.4177" font-family="ING Me" font-size="22.00" fill="#ffffff">
|
||||
SendInvoice
|
||||
</text>
|
||||
</g>
|
||||
<!-- SendInvoice->InvoiceSent -->
|
||||
<g id="edge1" class="edge">
|
||||
<title>SendInvoice->InvoiceSent</title>
|
||||
<path fill="none" stroke="#000000"
|
||||
d="M156.332,-179.5853C156.332,-171.4709 156.332,-162.8917 156.332,-154.2606"/>
|
||||
<polygon fill="#000000" stroke="#000000"
|
||||
points="159.8321,-154.1294 156.332,-144.1295 152.8321,-154.1295 159.8321,-154.1294"/>
|
||||
</g>
|
||||
<!-- customerInfo -->
|
||||
<g id="node3" class="node">
|
||||
<title>customerInfo</title>
|
||||
<ellipse fill="#ee0000" stroke="#ee0000" stroke-width="5" cx="156.332" cy="-420.3272" rx="104.1195"
|
||||
ry="104.1195"/>
|
||||
<text text-anchor="middle" x="156.332" y="-416.7302" font-family="ING Me" font-size="22.00" fill="#ffffff">
|
||||
customerInfo
|
||||
</text>
|
||||
</g>
|
||||
<!-- customerInfo->SendInvoice -->
|
||||
<g id="edge2" class="edge">
|
||||
<title>customerInfo->SendInvoice</title>
|
||||
<path fill="none" stroke="#000000"
|
||||
d="M156.332,-315.984C156.332,-307.2183 156.332,-298.5575 156.332,-290.3029"/>
|
||||
<polygon fill="#000000" stroke="#000000"
|
||||
points="159.8321,-290.087 156.332,-280.0871 152.8321,-290.0871 159.8321,-290.087"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.3 KiB |
BIN
docs/sensory-and-system-events.png
Normal file
|
After Width: | Height: | Size: 83 KiB |
BIN
docs/sequence-parallel.png
Normal file
|
After Width: | Height: | Size: 31 KiB |
BIN
docs/sequence.png
Normal file
|
After Width: | Height: | Size: 29 KiB |
21
docs/sequence.puml
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
@startuml
|
||||
actor Customer as C
|
||||
participant "Web-shop API" as API
|
||||
participant ERP
|
||||
participant Factory
|
||||
participant PostOffice
|
||||
participant Accountant
|
||||
C -> API : Lookup product
|
||||
activate API
|
||||
API -> ERP : Validate Order (check availability)
|
||||
API -> C : OK
|
||||
deactivate API
|
||||
C -> API : Order product
|
||||
activate API
|
||||
API -> C : OK
|
||||
API -> ERP : Validate Order (check availability)
|
||||
API -> Factory : Manufacture Product
|
||||
API -> PostOffice : Ship Product
|
||||
API -> Accountant : Invoice Customer
|
||||
deactivate API
|
||||
@enduml
|
||||
BIN
docs/ship-goods.png
Normal file
|
After Width: | Height: | Size: 24 KiB |
68
docs/ship-goods.svg
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
<svg width="405pt" height="553pt"
|
||||
viewBox="0.00 0.00 405.07 553.44" xmlns="http://www.w3.org/2000/svg">
|
||||
<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(14.4 539.0368)">
|
||||
<title>%3</title>
|
||||
<polygon fill="#ffffff" stroke="transparent"
|
||||
points="-14.4,14.4 -14.4,-539.0368 390.6736,-539.0368 390.6736,14.4 -14.4,14.4"/>
|
||||
<!-- ShipGoods -->
|
||||
<g id="node1" class="node">
|
||||
<title>ShipGoods</title>
|
||||
<path fill="#525199" stroke="#525199" stroke-width="2"
|
||||
d="M259.49,-280.0206C259.49,-280.0206 108.438,-280.0206 108.438,-280.0206 102.438,-280.0206 96.438,-274.0206 96.438,-268.0206 96.438,-268.0206 96.438,-192.0088 96.438,-192.0088 96.438,-186.0088 102.438,-180.0088 108.438,-180.0088 108.438,-180.0088 259.49,-180.0088 259.49,-180.0088 265.49,-180.0088 271.49,-186.0088 271.49,-192.0088 271.49,-192.0088 271.49,-268.0206 271.49,-268.0206 271.49,-274.0206 265.49,-280.0206 259.49,-280.0206"/>
|
||||
<text text-anchor="middle" x="183.964" y="-226.4177" font-family="ING Me" font-size="22.00" fill="#ffffff">
|
||||
ShipGoods
|
||||
</text>
|
||||
</g>
|
||||
<!-- GoodsShipped -->
|
||||
<g id="node2" class="node">
|
||||
<title>GoodsShipped</title>
|
||||
<path fill="#767676" stroke="#767676"
|
||||
d="M172.7899,-139.6428C172.7899,-139.6428 11.2101,-76.3808 11.2101,-76.3808 5.623,-74.1933 5.623,-69.8184 11.2101,-67.631 11.2101,-67.631 172.7899,-4.369 172.7899,-4.369 178.377,-2.1816 189.551,-2.1816 195.1381,-4.369 195.1381,-4.369 356.7179,-67.631 356.7179,-67.631 362.305,-69.8184 362.305,-74.1933 356.7179,-76.3808 356.7179,-76.3808 195.1381,-139.6428 195.1381,-139.6428 189.551,-141.8302 178.377,-141.8302 172.7899,-139.6428"/>
|
||||
<text text-anchor="middle" x="183.964" y="-68.4088" font-family="ING Me" font-size="22.00" fill="#ffffff">
|
||||
GoodsShipped
|
||||
</text>
|
||||
</g>
|
||||
<!-- ShipGoods->GoodsShipped -->
|
||||
<g id="edge1" class="edge">
|
||||
<title>ShipGoods->GoodsShipped</title>
|
||||
<path fill="none" stroke="#000000"
|
||||
d="M183.964,-179.5853C183.964,-171.4709 183.964,-162.8917 183.964,-154.2606"/>
|
||||
<polygon fill="#000000" stroke="#000000"
|
||||
points="187.4641,-154.1294 183.964,-144.1295 180.4641,-154.1295 187.4641,-154.1294"/>
|
||||
</g>
|
||||
<!-- goods -->
|
||||
<g id="node3" class="node">
|
||||
<title>goods</title>
|
||||
<ellipse fill="#ee0000" stroke="#ee0000" stroke-width="5" cx="95.964" cy="-420.3272" rx="53.8479"
|
||||
ry="53.8479"/>
|
||||
<text text-anchor="middle" x="95.964" y="-416.7302" font-family="ING Me" font-size="22.00" fill="#ffffff">
|
||||
goods
|
||||
</text>
|
||||
</g>
|
||||
<!-- goods->ShipGoods -->
|
||||
<g id="edge2" class="edge">
|
||||
<title>goods->ShipGoods</title>
|
||||
<path fill="none" stroke="#000000"
|
||||
d="M118.6262,-371.317C130.197,-346.2935 144.3654,-315.6524 156.5472,-289.3074"/>
|
||||
<polygon fill="#000000" stroke="#000000"
|
||||
points="159.7455,-290.7299 160.7657,-280.1843 153.3918,-287.792 159.7455,-290.7299"/>
|
||||
</g>
|
||||
<!-- customerInfo -->
|
||||
<g id="node4" class="node">
|
||||
<title>customerInfo</title>
|
||||
<ellipse fill="#ee0000" stroke="#ee0000" stroke-width="5" cx="271.964" cy="-420.3272" rx="104.1195"
|
||||
ry="104.1195"/>
|
||||
<text text-anchor="middle" x="271.964" y="-416.7302" font-family="ING Me" font-size="22.00" fill="#ffffff">
|
||||
customerInfo
|
||||
</text>
|
||||
</g>
|
||||
<!-- customerInfo->ShipGoods -->
|
||||
<g id="edge3" class="edge">
|
||||
<title>customerInfo->ShipGoods</title>
|
||||
<path fill="none" stroke="#000000"
|
||||
d="M228.0929,-325.4498C222.4423,-313.2294 216.7888,-301.003 211.5053,-289.5766"/>
|
||||
<polygon fill="#000000" stroke="#000000"
|
||||
points="214.5362,-287.792 207.1623,-280.1843 208.1825,-290.7299 214.5362,-287.792"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 4.1 KiB |
BIN
docs/validate-order.png
Normal file
|
After Width: | Height: | Size: 23 KiB |
68
docs/validate-order.svg
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
<svg width="665pt" height="441pt"
|
||||
viewBox="0.00 0.00 664.50 441.12" xmlns="http://www.w3.org/2000/svg">
|
||||
<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(14.4 426.72)">
|
||||
<title>%3</title>
|
||||
<polygon fill="#ffffff" stroke="transparent"
|
||||
points="-14.4,14.4 -14.4,-426.72 650.104,-426.72 650.104,14.4 -14.4,14.4"/>
|
||||
<!-- OrderValid -->
|
||||
<g id="node1" class="node">
|
||||
<title>OrderValid</title>
|
||||
<path fill="#767676" stroke="#767676"
|
||||
d="M135.337,-138.7154C135.337,-138.7154 10.663,-77.3081 10.663,-77.3081 5.2805,-74.657 5.2805,-69.3548 10.663,-66.7036 10.663,-66.7036 135.337,-5.2964 135.337,-5.2964 140.7195,-2.6452 151.4845,-2.6452 156.867,-5.2964 156.867,-5.2964 281.541,-66.7036 281.541,-66.7036 286.9235,-69.3548 286.9235,-74.657 281.541,-77.3081 281.541,-77.3081 156.867,-138.7154 156.867,-138.7154 151.4845,-141.3665 140.7195,-141.3665 135.337,-138.7154"/>
|
||||
<text text-anchor="middle" x="146.102" y="-68.4088" font-family="ING Me" font-size="22.00" fill="#ffffff">
|
||||
OrderValid
|
||||
</text>
|
||||
</g>
|
||||
<!-- OrderInvalid -->
|
||||
<g id="node2" class="node">
|
||||
<title>OrderInvalid</title>
|
||||
<path fill="#767676" stroke="#767676"
|
||||
d="M462.1287,-139.161C462.1287,-139.161 321.3712,-76.8626 321.3712,-76.8626 315.8846,-74.4342 315.8846,-69.5775 321.3712,-67.1492 321.3712,-67.1492 462.1287,-4.8508 462.1287,-4.8508 467.6154,-2.4225 478.5886,-2.4225 484.0753,-4.8508 484.0753,-4.8508 624.8328,-67.1492 624.8328,-67.1492 630.3194,-69.5775 630.3194,-74.4342 624.8328,-76.8626 624.8328,-76.8626 484.0753,-139.161 484.0753,-139.161 478.5886,-141.5893 467.6154,-141.5893 462.1287,-139.161"/>
|
||||
<text text-anchor="middle" x="473.102" y="-68.4088" font-family="ING Me" font-size="22.00" fill="#ffffff">
|
||||
OrderInvalid
|
||||
</text>
|
||||
</g>
|
||||
<!-- order -->
|
||||
<g id="node3" class="node">
|
||||
<title>order</title>
|
||||
<ellipse fill="#ee0000" stroke="#ee0000" stroke-width="5" cx="309.102" cy="-364.1688" rx="48.3028"
|
||||
ry="48.3028"/>
|
||||
<text text-anchor="middle" x="309.102" y="-360.5718" font-family="ING Me" font-size="22.00" fill="#ffffff">
|
||||
order
|
||||
</text>
|
||||
</g>
|
||||
<!-- ValidateOrder -->
|
||||
<g id="node4" class="node">
|
||||
<title>ValidateOrder</title>
|
||||
<path fill="#525199" stroke="#525199" stroke-width="2"
|
||||
d="M400.5445,-280.0206C400.5445,-280.0206 217.6595,-280.0206 217.6595,-280.0206 211.6595,-280.0206 205.6595,-274.0206 205.6595,-268.0206 205.6595,-268.0206 205.6595,-192.0088 205.6595,-192.0088 205.6595,-186.0088 211.6595,-180.0088 217.6595,-180.0088 217.6595,-180.0088 400.5445,-180.0088 400.5445,-180.0088 406.5445,-180.0088 412.5445,-186.0088 412.5445,-192.0088 412.5445,-192.0088 412.5445,-268.0206 412.5445,-268.0206 412.5445,-274.0206 406.5445,-280.0206 400.5445,-280.0206"/>
|
||||
<text text-anchor="middle" x="309.102" y="-226.4177" font-family="ING Me" font-size="22.00" fill="#ffffff">
|
||||
ValidateOrder
|
||||
</text>
|
||||
</g>
|
||||
<!-- order->ValidateOrder -->
|
||||
<g id="edge1" class="edge">
|
||||
<title>order->ValidateOrder</title>
|
||||
<path fill="none" stroke="#000000"
|
||||
d="M309.102,-315.7361C309.102,-307.519 309.102,-298.8965 309.102,-290.4176"/>
|
||||
<polygon fill="#000000" stroke="#000000"
|
||||
points="312.6021,-290.1834 309.102,-280.1834 305.6021,-290.1834 312.6021,-290.1834"/>
|
||||
</g>
|
||||
<!-- ValidateOrder->OrderValid -->
|
||||
<g id="edge2" class="edge">
|
||||
<title>ValidateOrder->OrderValid</title>
|
||||
<path fill="none" stroke="#000000"
|
||||
d="M257.0796,-179.5853C239.7966,-162.8315 220.4695,-144.0963 202.8483,-127.0146"/>
|
||||
<polygon fill="#000000" stroke="#000000"
|
||||
points="205.0279,-124.2529 195.4117,-119.8057 200.1557,-129.279 205.0279,-124.2529"/>
|
||||
</g>
|
||||
<!-- ValidateOrder->OrderInvalid -->
|
||||
<g id="edge3" class="edge">
|
||||
<title>ValidateOrder->OrderInvalid</title>
|
||||
<path fill="none" stroke="#000000"
|
||||
d="M361.4435,-179.5853C378.3405,-163.3056 397.1792,-145.1551 414.4982,-128.4688"/>
|
||||
<polygon fill="#000000" stroke="#000000"
|
||||
points="417.0446,-130.8757 421.8176,-121.4168 412.1877,-125.8347 417.0446,-130.8757"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 4.5 KiB |
BIN
docs/wkshop_evaluation.pdf
Normal file
BIN
docs/workshop.jpg
Normal file
|
After Width: | Height: | Size: 2 MiB |
178
pom.xml
Normal file
|
|
@ -0,0 +1,178 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<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>com.ing.baker.tutorials</groupId>
|
||||
<artifactId>workshop</artifactId>
|
||||
<version>1.1-SNAPSHOT</version>
|
||||
|
||||
<name>workshop</name>
|
||||
<url>https://github.com/ing-bank/baker</url>
|
||||
|
||||
<properties>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
<maven.compiler.source>1.8</maven.compiler.source>
|
||||
<maven.compiler.target>1.8</maven.compiler.target>
|
||||
<apache.batik.version>1.10</apache.batik.version>
|
||||
<mockito.version>1.10.19</mockito.version>
|
||||
<baker.version>3.3.3</baker.version>
|
||||
<kotlin.version>1.6.10</kotlin.version>
|
||||
<junit-jupiter.version>5.4.2</junit-jupiter.version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.jetbrains.kotlin</groupId>
|
||||
<artifactId>kotlin-stdlib</artifactId>
|
||||
<version>${kotlin.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.jetbrains.kotlin</groupId>
|
||||
<artifactId>kotlin-test</artifactId>
|
||||
<version>${kotlin.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.junit.jupiter</groupId>
|
||||
<artifactId>junit-jupiter-api</artifactId>
|
||||
<version>${junit-jupiter.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.junit.jupiter</groupId>
|
||||
<artifactId>junit-jupiter-engine</artifactId>
|
||||
<version>${junit-jupiter.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<!--to be able to visualize a recipe as SVG-->
|
||||
<dependency>
|
||||
<groupId>guru.nidi</groupId>
|
||||
<artifactId>graphviz-java</artifactId>
|
||||
<version>0.5.2</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<!--two extra dependencies when visualizing the recipe as PNG-->
|
||||
<dependency>
|
||||
<groupId>org.apache.xmlgraphics</groupId>
|
||||
<artifactId>batik-i18n</artifactId>
|
||||
<version>${apache.batik.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.xmlgraphics</groupId>
|
||||
<artifactId>batik-constants</artifactId>
|
||||
<version>${apache.batik.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<!--to be able to mock interactions -->
|
||||
<dependency>
|
||||
<groupId>org.mockito</groupId>
|
||||
<artifactId>mockito-core</artifactId>
|
||||
<version>${mockito.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.mockito.kotlin</groupId>
|
||||
<artifactId>mockito-kotlin</artifactId>
|
||||
<version>4.0.0</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<!--Baker dependencies follow -->
|
||||
<dependency>
|
||||
<groupId>com.ing.baker</groupId>
|
||||
<artifactId>baker-recipe-dsl_2.12</artifactId>
|
||||
<version>${baker.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.ing.baker</groupId>
|
||||
<artifactId>baker-runtime_2.12</artifactId>
|
||||
<version>${baker.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.ing.baker</groupId>
|
||||
<artifactId>baker-compiler_2.12</artifactId>
|
||||
<version>${baker.version}</version>
|
||||
</dependency>
|
||||
<!-- Lombok for less boilerplate code in events POJOs and unit tests-->
|
||||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
<version>1.16.16</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.google.guava</groupId>
|
||||
<artifactId>guava</artifactId>
|
||||
<version>29.0-jre</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<pluginManagement><!-- lock down plugins versions to avoid using Maven defaults (may be moved to parent pom) -->
|
||||
<plugins>
|
||||
<plugin>
|
||||
<artifactId>maven-clean-plugin</artifactId>
|
||||
<version>3.0.0</version>
|
||||
</plugin>
|
||||
<!-- see http://maven.apache.org/ref/current/maven-core/default-bindings.html#Plugin_bindings_for_jar_packaging -->
|
||||
<plugin>
|
||||
<artifactId>maven-resources-plugin</artifactId>
|
||||
<version>3.0.2</version>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<version>3.7.0</version>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<artifactId>maven-surefire-plugin</artifactId>
|
||||
<version>2.20.1</version>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<artifactId>maven-jar-plugin</artifactId>
|
||||
<version>3.0.2</version>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<artifactId>maven-install-plugin</artifactId>
|
||||
<version>2.5.2</version>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<artifactId>maven-deploy-plugin</artifactId>
|
||||
<version>2.8.2</version>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</pluginManagement>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.jetbrains.kotlin</groupId>
|
||||
<artifactId>kotlin-maven-plugin</artifactId>
|
||||
<version>${kotlin.version}</version>
|
||||
|
||||
<executions>
|
||||
<execution>
|
||||
<id>compile</id>
|
||||
<goals>
|
||||
<goal>compile</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
|
||||
<execution>
|
||||
<id>test-compile</id>
|
||||
<goals>
|
||||
<goal>test-compile</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<configuration>
|
||||
<source>8</source>
|
||||
<target>8</target>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
26
src/main/kotlin/com/ing/baker/tutorials/WebShopRecipe.kt
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
package com.ing.baker.tutorials
|
||||
|
||||
import com.ing.baker.recipe.javadsl.InteractionDescriptor
|
||||
import com.ing.baker.recipe.javadsl.Recipe
|
||||
import com.ing.baker.tutorials.events.SensoryEvents.*
|
||||
import com.ing.baker.tutorials.events.ShipGoodsEvents
|
||||
import com.ing.baker.tutorials.interactions.ManufactureGoods
|
||||
import com.ing.baker.tutorials.interactions.SendInvoice
|
||||
import com.ing.baker.tutorials.interactions.ShipGoods
|
||||
import com.ing.baker.tutorials.interactions.ValidateOrder
|
||||
|
||||
class WebShopRecipe {
|
||||
companion object {
|
||||
fun getRecipe(): Recipe {
|
||||
return Recipe("Webshop")
|
||||
.withSensoryEvents(OrderPlaced::class.java, PaymentMade::class.java, CustomerInfoReceived::class.java)
|
||||
.withInteractions(
|
||||
InteractionDescriptor.of(ValidateOrder::class.java),
|
||||
InteractionDescriptor.of(ManufactureGoods::class.java).withRequiredEvent(PaymentMade::class.java),
|
||||
InteractionDescriptor.of(ShipGoods::class.java),
|
||||
InteractionDescriptor.of(SendInvoice::class.java)
|
||||
.withRequiredEvent(ShipGoodsEvents.GoodsShipped::class.java)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
package com.ing.baker.tutorials.events
|
||||
|
||||
class ManufactureGoodsEvents {
|
||||
|
||||
interface ManufactureOutcome
|
||||
|
||||
class GoodsManufactured(val goods: String): ManufactureOutcome
|
||||
}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
package com.ing.baker.tutorials.events
|
||||
|
||||
class SendInvoiceEvents {
|
||||
|
||||
interface InvoicingOutcome
|
||||
|
||||
class InvoiceSent: InvoicingOutcome
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
package com.ing.baker.tutorials.events
|
||||
|
||||
import com.ing.baker.tutorials.model.CustomerInfo
|
||||
|
||||
class SensoryEvents {
|
||||
|
||||
class OrderPlaced(val order: String)
|
||||
|
||||
class CustomerInfoReceived(val customerInfo: CustomerInfo)
|
||||
|
||||
class PaymentMade
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
package com.ing.baker.tutorials.events
|
||||
|
||||
class ShipGoodsEvents {
|
||||
interface ShippingOutcome
|
||||
|
||||
class GoodsShipped: ShippingOutcome
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
package com.ing.baker.tutorials.events
|
||||
|
||||
class ValidateOrderEvents {
|
||||
interface ValidateOrderOutcome
|
||||
|
||||
class OrderValid: ValidateOrderOutcome
|
||||
|
||||
class OrderInvalid: ValidateOrderOutcome
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
package com.ing.baker.tutorials.interactions
|
||||
|
||||
import com.ing.baker.recipe.annotations.FiresEvent
|
||||
import com.ing.baker.recipe.annotations.RequiresIngredient
|
||||
import com.ing.baker.recipe.javadsl.Interaction
|
||||
import com.ing.baker.tutorials.events.ManufactureGoodsEvents.GoodsManufactured
|
||||
import com.ing.baker.tutorials.events.ManufactureGoodsEvents.ManufactureOutcome
|
||||
|
||||
interface ManufactureGoods: Interaction {
|
||||
@FiresEvent(oneOf = [GoodsManufactured::class])
|
||||
@Throws(Exception::class)
|
||||
fun apply(@RequiresIngredient("order") order: String?): ManufactureOutcome
|
||||
}
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
package com.ing.baker.tutorials.interactions
|
||||
|
||||
import com.ing.baker.recipe.annotations.FiresEvent
|
||||
import com.ing.baker.recipe.annotations.RequiresIngredient
|
||||
import com.ing.baker.recipe.javadsl.Interaction
|
||||
import com.ing.baker.tutorials.events.SendInvoiceEvents
|
||||
import com.ing.baker.tutorials.events.SendInvoiceEvents.InvoicingOutcome
|
||||
import com.ing.baker.tutorials.model.CustomerInfo
|
||||
|
||||
interface SendInvoice : Interaction {
|
||||
|
||||
@FiresEvent(oneOf = [SendInvoiceEvents.InvoiceSent::class])
|
||||
fun apply(@RequiresIngredient("customerInfo") customerInfo: CustomerInfo): InvoicingOutcome
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
package com.ing.baker.tutorials.interactions
|
||||
|
||||
import com.ing.baker.recipe.annotations.FiresEvent
|
||||
import com.ing.baker.recipe.annotations.RequiresIngredient
|
||||
import com.ing.baker.recipe.javadsl.Interaction
|
||||
import com.ing.baker.tutorials.events.ShipGoodsEvents.GoodsShipped
|
||||
import com.ing.baker.tutorials.model.CustomerInfo
|
||||
|
||||
interface ShipGoods : Interaction {
|
||||
|
||||
@FiresEvent(oneOf = [GoodsShipped::class])
|
||||
fun apply(
|
||||
@RequiresIngredient("goods") goods: String,
|
||||
@RequiresIngredient("customerInfo") customerInfo: CustomerInfo
|
||||
): GoodsShipped
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
package com.ing.baker.tutorials.interactions
|
||||
|
||||
import com.ing.baker.recipe.annotations.FiresEvent
|
||||
import com.ing.baker.recipe.annotations.RequiresIngredient
|
||||
import com.ing.baker.recipe.javadsl.Interaction
|
||||
import com.ing.baker.tutorials.events.ValidateOrderEvents.*
|
||||
|
||||
interface ValidateOrder: Interaction {
|
||||
|
||||
@FiresEvent(oneOf = [OrderValid::class, OrderInvalid::class])
|
||||
fun apply(@RequiresIngredient("order") order: String): ValidateOrderOutcome
|
||||
}
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
package com.ing.baker.tutorials.interactions
|
||||
|
||||
import com.ing.baker.tutorials.events.ValidateOrderEvents
|
||||
|
||||
class ValidateOrderImpl: ValidateOrder {
|
||||
override fun apply(order: String): ValidateOrderEvents.ValidateOrderOutcome {
|
||||
print("Validation order...")
|
||||
return ValidateOrderEvents.OrderValid()
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
package com.ing.baker.tutorials.model
|
||||
|
||||
data class CustomerInfo(val info: String)
|
||||
129
src/test/kotlin/com/ing/baker/tutorials/WebShopRecipeTest.kt
Normal file
|
|
@ -0,0 +1,129 @@
|
|||
package com.ing.baker.tutorials
|
||||
|
||||
import akka.actor.ActorSystem
|
||||
import com.ing.baker.compiler.RecipeCompiler
|
||||
import com.ing.baker.runtime.akka.AkkaBaker
|
||||
import com.ing.baker.runtime.javadsl.EventInstance
|
||||
import com.ing.baker.runtime.javadsl.InteractionInstance
|
||||
import com.ing.baker.tutorials.events.ManufactureGoodsEvents.GoodsManufactured
|
||||
import com.ing.baker.tutorials.events.SendInvoiceEvents.InvoiceSent
|
||||
import com.ing.baker.tutorials.events.SensoryEvents
|
||||
import com.ing.baker.tutorials.events.ShipGoodsEvents.GoodsShipped
|
||||
import com.ing.baker.tutorials.events.ValidateOrderEvents.OrderValid
|
||||
import com.ing.baker.tutorials.interactions.ManufactureGoods
|
||||
import com.ing.baker.tutorials.interactions.SendInvoice
|
||||
import com.ing.baker.tutorials.interactions.ShipGoods
|
||||
import com.ing.baker.tutorials.interactions.ValidateOrder
|
||||
import com.ing.baker.tutorials.model.CustomerInfo
|
||||
import guru.nidi.graphviz.engine.Format
|
||||
import guru.nidi.graphviz.engine.Graphviz
|
||||
import guru.nidi.graphviz.parse.Parser
|
||||
import org.junit.jupiter.api.AfterEach
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.mockito.Mockito.*
|
||||
import org.mockito.kotlin.mock
|
||||
import org.mockito.kotlin.times
|
||||
import org.mockito.kotlin.verify
|
||||
import scala.collection.mutable.ArraySeq
|
||||
import scala.concurrent.Await
|
||||
import scala.concurrent.duration.Duration
|
||||
import java.io.File
|
||||
import java.util.*
|
||||
import java.util.concurrent.TimeUnit
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertNotNull
|
||||
|
||||
class WebShopRecipeTest {
|
||||
private val recipe = WebShopRecipe.getRecipe()
|
||||
private val mockValidateOrder = mock<ValidateOrder>()
|
||||
private val mockManufactureGoods = mock<ManufactureGoods>()
|
||||
private val mockShipGoods = mock<ShipGoods>()
|
||||
private val mockSendInvoice = mock<SendInvoice>()
|
||||
|
||||
private val actorSystem = ActorSystem.create("WebShop")
|
||||
private val baker = AkkaBaker.javaLocalDefault(actorSystem, listOf(InteractionInstance.from(mockValidateOrder),
|
||||
InteractionInstance.from(mockManufactureGoods),
|
||||
InteractionInstance.from(mockShipGoods),
|
||||
InteractionInstance.from(mockSendInvoice)))
|
||||
|
||||
private var recipeId: String
|
||||
|
||||
private val orderId = "ORD-123456"
|
||||
private val customerInfo = CustomerInfo("John Doe, Amsterdam")
|
||||
private val goods = "this is the product"
|
||||
|
||||
init {
|
||||
val compiledRecipe = RecipeCompiler.compileRecipe(recipe)
|
||||
recipeId = baker.addRecipe(compiledRecipe, true).get()
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
fun stopActorSystem() {
|
||||
Await.ready(actorSystem.terminate(), Duration.apply(20, TimeUnit.SECONDS))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun shouldVisualiseTheRecipe() {
|
||||
val compiledRecipe = RecipeCompiler.compileRecipe(recipe)
|
||||
val visualRecipe = compiledRecipe.recipeVisualization
|
||||
saveVisualizationAsSvg(visualRecipe)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun shouldHaveNoValidationErrors() {
|
||||
val compiledRecipe = RecipeCompiler.compileRecipe(recipe)
|
||||
assertEquals(compiledRecipe.validationErrors(), ArraySeq.empty())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun shouldBakeTheRecipe() {
|
||||
val processId = UUID.randomUUID().toString()
|
||||
baker.bake(recipeId, processId)
|
||||
val accumulatedState = baker.getIngredients(processId)
|
||||
assertNotNull(accumulatedState)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun shouldExecuteHappyFowCorrectly() {
|
||||
setupMocks()
|
||||
|
||||
val processId = UUID.randomUUID().toString()
|
||||
baker.bake(recipeId, processId)
|
||||
|
||||
baker.fireEventAndResolveWhenCompleted(processId, EventInstance.from(SensoryEvents.CustomerInfoReceived(customerInfo))).get()
|
||||
verify(mockValidateOrder, never()).apply(anyString())
|
||||
verify(mockManufactureGoods, never()).apply(anyString())
|
||||
verify(mockShipGoods, never()).apply(goods, customerInfo)
|
||||
verify(mockSendInvoice, never()).apply(customerInfo)
|
||||
|
||||
baker.fireEventAndResolveWhenCompleted(processId, EventInstance.from(SensoryEvents.OrderPlaced(orderId))).get()
|
||||
verify(mockValidateOrder).apply(orderId)
|
||||
verify(mockManufactureGoods, never()).apply(anyString())
|
||||
verify(mockShipGoods, never()).apply(goods, customerInfo)
|
||||
verify(mockSendInvoice, never()).apply(customerInfo)
|
||||
|
||||
baker.fireEventAndResolveWhenCompleted(processId, EventInstance.from(SensoryEvents.PaymentMade())).get()
|
||||
verify(mockManufactureGoods).apply(orderId)
|
||||
verify(mockShipGoods).apply(goods, customerInfo)
|
||||
verify(mockSendInvoice).apply(customerInfo)
|
||||
|
||||
verify(mockValidateOrder, times(1)).apply(orderId)
|
||||
verify(mockManufactureGoods, times(1)).apply(orderId)
|
||||
verify(mockShipGoods, times(1)).apply(goods, customerInfo)
|
||||
verify(mockSendInvoice, times(1)).apply(customerInfo)
|
||||
}
|
||||
|
||||
private fun saveVisualizationAsSvg(dotGraph: String) {
|
||||
val file = File("./target/" + "web-shop-recipe.svg")
|
||||
val g = Parser.read(dotGraph)
|
||||
Graphviz.fromGraph(g).render(Format.SVG).toFile(file)
|
||||
}
|
||||
|
||||
private fun setupMocks() {
|
||||
`when`(mockValidateOrder.apply(orderId)).thenReturn(OrderValid())
|
||||
`when`(mockManufactureGoods.apply(orderId)).thenReturn(GoodsManufactured(goods))
|
||||
`when`(mockShipGoods.apply(goods, customerInfo)).thenReturn(GoodsShipped())
|
||||
`when`(mockSendInvoice.apply(customerInfo)).thenReturn(InvoiceSent())
|
||||
}
|
||||
|
||||
}
|
||||
1
src/test/resources/application.conf
Normal file
|
|
@ -0,0 +1 @@
|
|||
include "baker.conf"
|
||||