flowfulfillment v1examples/fulfillment/temporal/fulfillment.flow · dandori doc
Reserve stock for each line of an order, arrange the delivery, wait for the warehouse to pack it, and then tell the customer. The lines are reserved side by side, and when one is short, what was reserved is released. Written for Temporal: the warehouse's calls are activities dandori writes, by Connect; a workflow of the delivery team, on its own task queue, arranges the delivery (a child workflow, arrange_delivery.flow), and when it finds no next-day van the standard carrier is asked; and the packing request, the notice and the audit log are activities you write, the packing crew answering the callback by the update the generated client sends
flow| Line | Call | Calls | Retries | Timeout | When it fails |
|---|---|---|---|---|---|
| 73 | decision = urgency(…) | rule urgency.rule | 2 times, after 1 second and 2 (failure) | — | timeout, failure → the workflow fails |
| 75 | r = reserve_stock(…) | connect warehouse StockService/Reserve, key | 2 times every 1 second (busy) | — | busy, timeout, failure → the round fails, and then the workflow |
| 86 | release_stock(…) | connect warehouse StockService/Release, idempotent | — | — | timeout, failure → the round fails, and then the workflow |
| 95 | audit(…) | a task you write, idempotent | — | — | timeout, failure → the workflow fails |
| 98 | delivery = arrange_delivery(…) | flow arrange_delivery.flow | — | — | NoVan → line 99timeout, failure → line 102 |
| 100 | delivery = arrange_delivery(…) | flow arrange_delivery.flow | — | — | NoVan, timeout, failure → line 101 |
| 103 | packed = wait_for_packing(…) | a task you write, answered by a callback | — | 2 days | timeout → line 104failure → the workflow fails |
| 105 | notify(…) | a task you write | — | — | no_recipient → line 106timeout, failure → the workflow fails |
| Line | End |
|---|---|
| 88 | fail OutOfStock "Order {order.id} has lines the stock is short of" |
| 101 | fail DeliveryFailed "Could not arrange the delivery of order {order.id}" |
| 102 | fail DeliveryFailed "Could not arrange the delivery of order {order.id}" |
| 104 | fail PackingLate "No word of the packing in two days" |
| 107 | succeed reservations = results, tracking_number = delivery.tracking_number |
Pick a step to see what the checker knows there. Pick a scenario on the left to light up the way its run goes; the number beside a step is how often the run passes it.
fulfillment v1Reserve stock for each line of an order, arrange the delivery, wait for the warehouse to pack it, and then tell the customer. The lines are reserved side by side, and when one is short, what was reserved is released. Written for Temporal: the warehouse's calls are activities dandori writes, by Connect; a workflow of the delivery team, on its own task queue, arranges the delivery (a child workflow, arrange_delivery.flow), and when it finds no next-day van the standard carrier is asked; and the packing request, the notice and the audit log are activities you write, the packing crew answering the callback by the update the generated client sends
order: Orderreservations: list[warehouse.ReserveResponse], tracking_number: stringshop.v1.FulfillmentService (../specs/fulfillment.proto)Fulfill: starts a run; it can fail with OutOfStock, DeliveryFailed, PackingLateAnswerPacking: answers the callback (wait_for_packing)examples/fulfillment/temporal/fulfillment.flowdecision = urgency(…) line 73 73 let decision = urgency(member: order.member, amount: order.amount)
urgency.ruletimeout, failure → the workflow fails 4 use rule urgency from "../../order/rules/urgency.rule"
r = reserve_stock(…) line 75 75 let r = reserve_stock(sku: line.sku, quantity: line.quantity)
connect warehouse StockService/Reserve, keybusy, timeout, failure → the round fails, and then the workflow42 # The warehouse answers every reservation: secured with its id, or short when the stock is short. 43 task reserve_stock(sku: string, quantity: int) -> warehouse.ReserveResponse 44 connect warehouse "StockService/Reserve" 45 errors busy = resource_exhausted 46 key 47 retry 2 times every 1 second on busy
let any_short = false line 77 77 let any_short = false
match one.stock line 7979 match one.stock 80 short => let any_short = true 81 secured => pass
let any_short = true line 80 80 short => let any_short = true
match any_short line 8282 match any_short 83 true => 89 false => pass
match taken.id line 8585 match taken.id 86 some id => release_stock(id: id) 87 none => pass
release_stock(…) line 86 86 some id => release_stock(id: id)
connect warehouse StockService/Release, idempotenttimeout, failure → the round fails, and then the workflow49 task release_stock(id: string) 50 connect warehouse "StockService/Release" 51 idempotent
fail OutOfStock line 88 88 fail OutOfStock "Order {order.id} has lines the stock is short of"
let recipient = none line 90 90 let recipient: string? = none
match order.gift line 9191 match order.gift 92 some gift => 97 none => pass
let recipient = gift.recipient line 93 93 let recipient = gift.recipient
match gift.message line 9494 match gift.message 95 some words => audit(order_id: order.id, extra: {message: words, recipient: gift.recipient}) 96 none => pass
audit(…) line 95 95 some words => audit(order_id: order.id, extra: {message: words, recipient: gift.recipient})
idempotenttimeout, failure → the workflow fails69 task audit(order_id: string, extra: json) 70 idempotent
delivery = arrange_delivery(…) line 9898 let delivery = arrange_delivery(order_id: order.id, carrier: decision.carrier, recipient: recipient, extra: order.extra) 99 on NoVan => 102 on failure => fail DeliveryFailed "Could not arrange the delivery of order {order.id}"
flow arrange_delivery.flowNoVan → line 99timeout, failure → line 10253 # The delivery team's workflow arranges the delivery, on the workers of its own task queue. It is 54 # written as a .flow of its own, and the checker holds this task to its inputs, outputs and errors. 55 task arrange_delivery(order_id: string, carrier: urgency.carrier, recipient: string?, extra: json) -> Delivery 56 flow "../arrange_delivery.flow" 57 errors NoVan
delivery = arrange_delivery(…) line 100100 let delivery = arrange_delivery(order_id: order.id, carrier: standard, recipient: recipient, extra: order.extra) 101 on failure => fail DeliveryFailed "Could not arrange the delivery of order {order.id}"
flow arrange_delivery.flowNoVan, timeout, failure → line 10153 # The delivery team's workflow arranges the delivery, on the workers of its own task queue. It is 54 # written as a .flow of its own, and the checker holds this task to its inputs, outputs and errors. 55 task arrange_delivery(order_id: string, carrier: urgency.carrier, recipient: string?, extra: json) -> Delivery 56 flow "../arrange_delivery.flow" 57 errors NoVan
fail DeliveryFailed line 101 101 on failure => fail DeliveryFailed "Could not arrange the delivery of order {order.id}"
fail DeliveryFailed line 102 102 on failure => fail DeliveryFailed "Could not arrange the delivery of order {order.id}"
packed = wait_for_packing(…) line 103103 let packed = wait_for_packing(order_id: order.id, reservations: results) 104 on timeout => fail PackingLate "No word of the packing in two days"
timeout → line 104failure → the workflow fails59 # The packing crew's app gets the request and the callback's id from the function you write, 60 # and answers the callback when the boxes are packed. 61 task wait_for_packing(order_id: string, reservations: list[warehouse.ReserveResponse]) -> Packing 62 callback 63 timeout 2 days
fail PackingLate line 104 104 on timeout => fail PackingLate "No word of the packing in two days"
notify(…) line 105105 notify(order_id: order.id, text: "Order {order.id} went out by {decision.carrier} ({packed.boxes} boxes, tracking number {delivery.tracking_number})") 106 on no_recipient => pass
no_recipient → line 106timeout, failure → the workflow fails65 # The shop's own notice service. 66 task notify(order_id: string, text: string) 67 errors no_recipient
succeed reservations = results, tracking_number = deliv… line 107 107 succeed reservations = results, tracking_number = delivery.tracking_number
end: succeedsThe flow runs to its end, and the workflow succeeds.
let results = for line in order.lines at most 50 in parallel, 10 at a time line 74 74 let results = for line in order.lines at most 50 in parallel, 10 at a time
for one in results at most 50 line 78 78 for one in results at most 50
for taken in results at most 50 in parallel line 84 84 for taken in results at most 50 in parallel
decision = urgency(…) oklet results = for line in order.lines at most 50 in parallel, 10 at a timelet any_short = falsefor one in results at most 50match any_short → falselet recipient = nonematch order.gift → nonedelivery = arrange_delivery(…) okpacked = wait_for_packing(…) oknotify(…) oksucceed reservations = results, tracking_number = deliv…decision = urgency(…) failure failure failure → not handled heredecision = urgency(…) okdecision = urgency(…) oklet results = for line in order.lines at most 50 in parallel, 10 at a timer = reserve_stock(…) oklet any_short = falsefor one in results at most 50match one.stock → securedmatch any_short → falselet recipient = nonematch order.gift → some giftlet recipient = gift.recipientmatch gift.message → nonedelivery = arrange_delivery(…) NoVan → on NoVandelivery = arrange_delivery(…) okpacked = wait_for_packing(…) timeout → on timeoutfail PackingLatedecision = urgency(…) oklet results = for line in order.lines at most 50 in parallel, 10 at a timer = reserve_stock(…) failure → not handled herer = reserve_stock(…) failure → not handled heredecision = urgency(…) oklet results = for line in order.lines at most 50 in parallel, 10 at a timedecision = urgency(…) oklet results = for line in order.lines at most 50 in parallel, 10 at a timelet any_short = falsefor one in results at most 50match any_short → falselet recipient = nonematch order.gift → some giftlet recipient = gift.recipientmatch gift.message → some wordsaudit(…) okdelivery = arrange_delivery(…) failure → on failurefail DeliveryFaileddecision = urgency(…) oklet results = for line in order.lines at most 50 in parallel, 10 at a timelet any_short = falsefor one in results at most 50match any_short → falselet recipient = nonematch order.gift → nonedelivery = arrange_delivery(…) NoVan → on NoVandelivery = arrange_delivery(…) failure → on failurefail DeliveryFaileddecision = urgency(…) oklet results = for line in order.lines at most 50 in parallel, 10 at a timelet any_short = falsefor one in results at most 50match any_short → falselet recipient = nonematch order.gift → nonedelivery = arrange_delivery(…) okdecision = urgency(…) oklet results = for line in order.lines at most 50 in parallel, 10 at a timelet any_short = falsefor one in results at most 50match any_short → falselet recipient = nonematch order.gift → nonedelivery = arrange_delivery(…) okpacked = wait_for_packing(…) failure → not handled heredecision = urgency(…) oklet results = for line in order.lines at most 50 in parallel, 10 at a timelet any_short = falsefor one in results at most 50match any_short → falselet recipient = nonematch order.gift → nonedelivery = arrange_delivery(…) okpacked = wait_for_packing(…) okdecision = urgency(…) oklet results = for line in order.lines at most 50 in parallel, 10 at a timelet any_short = falsefor one in results at most 50match any_short → falselet recipient = nonematch order.gift → nonedelivery = arrange_delivery(…) okpacked = wait_for_packing(…) oknotify(…) no_recipient → on no_recipientsucceed reservations = results, tracking_number = deliv…decision = urgency(…) oklet results = for line in order.lines at most 50 in parallel, 10 at a timelet any_short = falsefor one in results at most 50match any_short → falselet recipient = nonematch order.gift → nonedelivery = arrange_delivery(…) okpacked = wait_for_packing(…) oknotify(…) failure → not handled heredecision = urgency(…) oklet results = for line in order.lines at most 50 in parallel, 10 at a timer = reserve_stock(…) busy oklet any_short = falsefor one in results at most 50match one.stock → shortlet any_short = truematch any_short → truefor taken in results at most 50 in parallelmatch taken.id → nonefail OutOfStockdecision = urgency(…) oklet results = for line in order.lines at most 50 in parallel, 10 at a timer = reserve_stock(…) okdecision = urgency(…) oklet results = for line in order.lines at most 50 in parallel, 10 at a timer = reserve_stock(…) okdecision = urgency(…) oklet results = for line in order.lines at most 50 in parallel, 10 at a timer = reserve_stock(…) oklet any_short = falsefor one in results at most 50match one.stock → shortlet any_short = truematch any_short → truefor taken in results at most 50 in parallelmatch taken.id → some idrelease_stock(…) okfail OutOfStockdecision = urgency(…) oklet results = for line in order.lines at most 50 in parallel, 10 at a timer = reserve_stock(…) oklet any_short = falsefor one in results at most 50match one.stock → securedmatch any_short → falselet recipient = nonematch order.gift → some giftlet recipient = gift.recipientmatch gift.message → some wordsaudit(…) failure → not handled heredecision = urgency(…) oklet results = for line in order.lines at most 50 in parallel, 10 at a timer = reserve_stock(…) oklet any_short = falsefor one in results at most 50match one.stock → securedmatch any_short → falselet recipient = nonematch order.gift → some giftlet recipient = gift.recipientmatch gift.message → nonedelivery = arrange_delivery(…) failure → on failurefail DeliveryFaileddecision = urgency(…) oklet results = for line in order.lines at most 50 in parallel, 10 at a timer = reserve_stock(…) oklet any_short = falsefor one in results at most 50match one.stock → securedmatch any_short → falselet recipient = nonematch order.gift → some giftlet recipient = gift.recipientmatch gift.message → nonedelivery = arrange_delivery(…) NoVan → on NoVandelivery = arrange_delivery(…) failure → on failurefail DeliveryFaileddecision = urgency(…) oklet results = for line in order.lines at most 50 in parallel, 10 at a timer = reserve_stock(…) oklet any_short = falsefor one in results at most 50match one.stock → securedmatch any_short → falselet recipient = nonematch order.gift → some giftlet recipient = gift.recipientmatch gift.message → nonedelivery = arrange_delivery(…) NoVan → on NoVandelivery = arrange_delivery(…) okdecision = urgency(…) oklet results = for line in order.lines at most 50 in parallel, 10 at a timer = reserve_stock(…) oklet any_short = falsefor one in results at most 50match one.stock → securedmatch any_short → falselet recipient = nonematch order.gift → some giftlet recipient = gift.recipientmatch gift.message → nonedelivery = arrange_delivery(…) NoVan → on NoVandelivery = arrange_delivery(…) okpacked = wait_for_packing(…) oknotify(…) oksucceed reservations = results, tracking_number = deliv…decision = urgency(…) oklet results = for line in order.lines at most 50 in parallel, 10 at a timer = reserve_stock(…) okr = reserve_stock(…) oklet any_short = falsefor one in results at most 50match one.stock → shortlet any_short = truematch one.stock → shortlet any_short = truematch any_short → truefor taken in results at most 50 in parallelmatch taken.id → nonematch taken.id → nonefail OutOfStockdecision = urgency(…) oklet results = for line in order.lines at most 50 in parallel, 10 at a timer = reserve_stock(…) okr = reserve_stock(…) oklet any_short = falsefor one in results at most 50match one.stock → shortlet any_short = truematch one.stock → shortlet any_short = truematch any_short → truefor taken in results at most 50 in parallelmatch taken.id → some idrelease_stock(…) failure → not handled herematch taken.id → some idrelease_stock(…) failure → not handled heredecision = urgency(…) oklet results = for line in order.lines at most 50 in parallel, 10 at a timer = reserve_stock(…) okr = reserve_stock(…) oklet any_short = falsefor one in results at most 50match one.stock → securedmatch one.stock → securedmatch any_short → falselet recipient = nonematch order.gift → some giftlet recipient = gift.recipientmatch gift.message → some wordsaudit(…) okdelivery = arrange_delivery(…) failure → on failurefail DeliveryFaileddecision = urgency(…) oklet results = for line in order.lines at most 50 in parallel, 10 at a timer = reserve_stock(…) okr = reserve_stock(…) oklet any_short = falsefor one in results at most 50match one.stock → securedmatch one.stock → securedmatch any_short → falselet recipient = nonematch order.gift → some giftlet recipient = gift.recipientmatch gift.message → nonedelivery = arrange_delivery(…) okpacked = wait_for_packing(…) oknotify(…) oksucceed reservations = results, tracking_number = deliv…decision = urgency(…) oklet results = for line in order.lines at most 50 in parallel, 10 at a timer = reserve_stock(…) okr = reserve_stock(…) oklet any_short = falsefor one in results at most 50match one.stock → securedmatch one.stock → securedmatch any_short → falselet recipient = nonematch order.gift → some giftlet recipient = gift.recipientmatch gift.message → some wordsaudit(…) okdelivery = arrange_delivery(…) NoVan → on NoVandelivery = arrange_delivery(…) okpacked = wait_for_packing(…) timeout → on timeoutfail PackingLatedecision = urgency(…) oklet results = for line in order.lines at most 50 in parallel, 10 at a timer = reserve_stock(…) okr = reserve_stock(…) oklet any_short = falsefor one in results at most 50match one.stock → securedmatch one.stock → securedmatch any_short → falselet recipient = nonematch order.gift → some giftlet recipient = gift.recipientmatch gift.message → nonedelivery = arrange_delivery(…) NoVan → on NoVandelivery = arrange_delivery(…) failure → on failurefail DeliveryFaileddecision = urgency(…) oklet results = for line in order.lines at most 50 in parallel, 10 at a timer = reserve_stock(…) okr = reserve_stock(…) oklet any_short = falsefor one in results at most 50match one.stock → securedmatch one.stock → securedmatch any_short → falselet recipient = nonematch order.gift → some giftlet recipient = gift.recipientmatch gift.message → some wordsaudit(…) okdelivery = arrange_delivery(…) NoVan → on NoVandelivery = arrange_delivery(…) okpacked = wait_for_packing(…) timeout → on timeoutfail PackingLate