initial commit

This commit is contained in:
Sander Hautvast 2022-03-08 09:31:05 +01:00
commit 4df4f6351f
41 changed files with 979 additions and 0 deletions

28
.gitignore vendored Normal file
View 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
View 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
View 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
View file

@ -0,0 +1,207 @@
![](workshop.jpg)
---
# 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` |
---
![original 270%](baker.png)
---
## 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
---
![left](donuts.jpg)
# Recipes
## Events
## Ingredients
## Interactions
---
![left, fit](sensory-and-system-events.png)
# What is an Event?
## It's what happens
## Sensory and System Events
---
![left, fit](ingredient.png)
# What is an Ingredient?
## It's a container for data
## Can be a primitive type or a POJO
---
![left, fit](interaction.png)
# 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]
![fit](sequence.png)
---
[.background-color: #FFFFFF]
![fit](validate-order.png)
---
[.background-color: #FFFFFF]
![fit](manufacture-goods.png)
---
[.background-color: #FFFFFF]
![fit](ship-goods.png)
---
[.background-color: #FFFFFF]
![fit](send-invoice.png)
---
<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]
![left, fit](sequence-parallel.png)
# 5. Call the Postoffice and Accountant Systems in parallel
---
# What Can Go Wrong?
---
[.background-color: #FFFFFF]
![left, fit](no-sensory-input.png)
# Recipe has no sensory input, Baker doesn't know where the data comes from
---
[.background-color: #FFFFFF]
![left, fit](incorrect-sequence.png)
# Which two interactions will be executed when the OrderPlaced event occurs? Is this the desired behavior?
---
# Desired Recipe
---
[.background-color: #FFFFFF]
![fit](desired-end-result.png)
---
# 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

Binary file not shown.

BIN
docs/baker.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

BIN
docs/desired-end-result.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 157 KiB

BIN
docs/donuts.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3 MiB

BIN
docs/incorrect-sequence.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 117 KiB

BIN
docs/ingredient.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 74 KiB

BIN
docs/interaction.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

BIN
docs/manufacture-goods.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

View 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&#45;&gt;goods -->
<g id="edge1" class="edge">
<title>GoodsManufactured&#45;&gt;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&#45;&gt;ManufactureGoods -->
<g id="edge2" class="edge">
<title>order&#45;&gt;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&#45;&gt;GoodsManufactured -->
<g id="edge3" class="edge">
<title>ManufactureGoods&#45;&gt;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

Binary file not shown.

After

Width:  |  Height:  |  Size: 84 KiB

BIN
docs/send-invoice.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

51
docs/send-invoice.svg Normal file
View 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&#45;&gt;InvoiceSent -->
<g id="edge1" class="edge">
<title>SendInvoice&#45;&gt;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&#45;&gt;SendInvoice -->
<g id="edge2" class="edge">
<title>customerInfo&#45;&gt;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

Binary file not shown.

After

Width:  |  Height:  |  Size: 83 KiB

BIN
docs/sequence-parallel.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

BIN
docs/sequence.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

21
docs/sequence.puml Normal file
View 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

68
docs/ship-goods.svg Normal file
View 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&#45;&gt;GoodsShipped -->
<g id="edge1" class="edge">
<title>ShipGoods&#45;&gt;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&#45;&gt;ShipGoods -->
<g id="edge2" class="edge">
<title>goods&#45;&gt;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&#45;&gt;ShipGoods -->
<g id="edge3" class="edge">
<title>customerInfo&#45;&gt;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

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

68
docs/validate-order.svg Normal file
View 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&#45;&gt;ValidateOrder -->
<g id="edge1" class="edge">
<title>order&#45;&gt;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&#45;&gt;OrderValid -->
<g id="edge2" class="edge">
<title>ValidateOrder&#45;&gt;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&#45;&gt;OrderInvalid -->
<g id="edge3" class="edge">
<title>ValidateOrder&#45;&gt;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

Binary file not shown.

BIN
docs/workshop.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2 MiB

178
pom.xml Normal file
View 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>

View 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)
)
}
}
}

View file

@ -0,0 +1,8 @@
package com.ing.baker.tutorials.events
class ManufactureGoodsEvents {
interface ManufactureOutcome
class GoodsManufactured(val goods: String): ManufactureOutcome
}

View file

@ -0,0 +1,8 @@
package com.ing.baker.tutorials.events
class SendInvoiceEvents {
interface InvoicingOutcome
class InvoiceSent: InvoicingOutcome
}

View file

@ -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
}

View file

@ -0,0 +1,7 @@
package com.ing.baker.tutorials.events
class ShipGoodsEvents {
interface ShippingOutcome
class GoodsShipped: ShippingOutcome
}

View file

@ -0,0 +1,9 @@
package com.ing.baker.tutorials.events
class ValidateOrderEvents {
interface ValidateOrderOutcome
class OrderValid: ValidateOrderOutcome
class OrderInvalid: ValidateOrderOutcome
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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()
}
}

View file

@ -0,0 +1,3 @@
package com.ing.baker.tutorials.model
data class CustomerInfo(val info: String)

View 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())
}
}

View file

@ -0,0 +1 @@
include "baker.conf"