Coffee Shop Example¶
This page explains examples/coffee_shop.py, a small workflow that models a coffee shop order. The example splits the process into Astrum tasks and uses TaskData / DataItem / DTRela to describe how data moves between tasks.
DAG overview¶
flowchart TD
take_order["take_order\nreturns order_id, drink, customer"]
grind_beans["grind_beans\ntakes drink_type"]
brew_coffee["brew_coffee\ntakes beans and drink_type"]
prepare_milk["prepare_milk\ntakes drink_type"]
assemble_drink["assemble_drink\ntakes coffee, milk, order_id"]
serve_customer["serve_customer\ntakes customer_name, drink, store_name"]
get_store_name["get_store_name()\nexternal injection"]
take_order -->|"drink -> drink_type"| grind_beans
grind_beans -->|"beans_ready -> beans"| brew_coffee
take_order -->|"drink -> drink_type"| brew_coffee
take_order -->|"drink -> drink_type"| prepare_milk
brew_coffee -->|"coffee_liquid -> coffee"| assemble_drink
prepare_milk -->|"milk_foam -> milk"| assemble_drink
take_order -->|"order_id -> order_id"| assemble_drink
assemble_drink -->|"final_drink -> drink"| serve_customer
take_order -->|"customer -> customer_name"| serve_customer
get_store_name -->|"store_name -> store_name"| serve_customer
There are two kinds of relationships:
- Task dependencies decide when each task may run.
- Data mappings decide which upstream values become downstream function arguments.
get_store_name is not a Astrum task. It is a regular function injected through from_function, so it contributes data to serve_customer without becoming a DAG node.
Execution stages¶
flowchart LR
stage0["Stage 0\ntake_order"]
stage1["Stage 1\ngrind_beans\nprepare_milk"]
stage2["Stage 2\nbrew_coffee"]
stage3["Stage 3\nassemble_drink"]
stage4["Stage 4\nserve_customer"]
stage0 --> stage1
stage1 --> stage2
stage2 --> stage3
stage3 --> stage4
grind_beans and prepare_milk only depend on take_order.drink, so they can start in parallel. brew_coffee waits for beans and drink type, assemble_drink gathers the brewed coffee, milk foam, and order id, and serve_customer performs the final delivery.
Task notes¶
| Task | Role | Important data |
|---|---|---|
take_order |
Entry task | Emits order_id, drink, and customer. |
grind_beans |
Preparation branch | Reads take_order.drink, emits beans_ready. |
brew_coffee |
Processing branch | Reads beans and drink type, emits coffee_liquid. |
prepare_milk |
Parallel preparation branch | Reads drink type, emits milk_foam. |
assemble_drink |
Fan-in node | Combines coffee, milk, and order id. |
serve_customer |
Final node | Reads customer, drink, and externally injected store name. |
Data flow matrix¶
| Downstream task | Parameter | Source |
|---|---|---|
grind_beans |
drink_type |
take_order.drink |
brew_coffee |
beans |
grind_beans.beans_ready |
brew_coffee |
drink_type |
take_order.drink |
prepare_milk |
drink_type |
take_order.drink |
assemble_drink |
coffee |
brew_coffee.coffee_liquid |
assemble_drink |
milk |
prepare_milk.milk_foam |
assemble_drink |
order_id |
take_order.order_id |
serve_customer |
customer_name |
take_order.customer |
serve_customer |
drink |
assemble_drink.final_drink |
serve_customer |
store_name |
get_store_name() |
How to read this example¶
Start by finding each @task(task_id=...), then inspect its TaskData.input_data_item. Finally, connect every from_relation to its matching to_relation. This is usually easier than reading the source top-to-bottom, because Astrum runs by dependency graph, not by file order.