Session 2: Happy Path
The team joins again to finalize the happy path and deep-dive into context variables and data flow.
Reviewing Session 1
Everyone's back on the video call. Alex shares their screen with the flow from last session. EventFlow watch mode is running.
bash
$ eventflow watch order.flow
EventFlow v1.0.0
➜ Editor: http://localhost:5173/
➜ Diagram: http://localhost:5173/diagram
➜ Tests: http://localhost:5173/tests
Watching for changes... Here's where we left off. Let me run the tests to make sure it still works.
bash
$ eventflow test order.flow
@order / checkout
happy path
✓ checkout → awaiting_payment (12ms)
✓ payment_success → confirmed (15ms)
2 passing Great, the happy path works. But Jordan had questions about preconditions.
Right. The given: block assumes customer is logged in and cart has items. But those aren't guards - they're just test setup.
Correct. The given: block sets up the test environment. The actual guards need to be in the event handler.
Discussing Preconditions vs Guards
What's the difference between given: and a guard?
Let me show you. given: is for test setup - it creates the world. Guards check conditions at runtime.
flow
// Test setup (given:) vs Runtime check (guard)
given:
@customer is logged in // Test setup: create a logged-in customer
cart has items // Test setup: create a cart with items
on> :checkout from @customer
? cart is not empty // Guard: runtime check with ? prefix
emit :payment_request to @payment So the given: tells the test framework what to set up, and the guard checks it when the event arrives?
Exactly. The `?` prefix marks a guard - a condition that must be true for the indented actions to run.
For the happy path, we assume everything is set up correctly. Edge cases come next.
Refining the Happy Path
Before we add edge cases, let me understand the happy path better. Walk me through what happens step by step.
Sure. Let me trace through the diagram.
Here's the flow:
- Customer sends :checkout to order
- Order generates $order_id, sends :payment_request to payment
- Payment processes (that's the external service)
- Payment sends :payment_success back to order
- Order sends confirmation email
- Order sends :order_confirmed to customer
At step 6, that's when the UI updates to show 'Order Confirmed'?
Exactly. The frontend listens for that event.
This is the ideal flow. Everything goes right.
Deep Dive: Context Variables
I want to understand the context variables better. We have $order_id and $total. How do they work?
Context variables start with $ and can have types. Let me show the different operations.
flow
// Context variable examples
// Creating/setting values
$order_id: string becomes uuid() // Generate a UUID
$total: number becomes 100 // Set to 100
$status: string becomes "pending" // Set to string
// Arithmetic operations
$total increases by 25 // Add 25
$balance decreases by $amount // Subtract
// Collection operations
$items: array adds "Laptop" // Add to array
$items removes "Mouse" // Remove from array
$cart clears // Empty the collection
// In given: blocks for test setup
given:
$total: number is 1200 // Initial value
$items: array is ["Laptop", "Mouse"] So `becomes` sets a value, and `is` is for test setup in given: blocks?
Yes. `becomes` is an action that runs at runtime. `is` describes the initial state for testing.
What about the types - string, number, array?
Types are optional but helpful. They document what kind of data the variable holds and can enable validation.
Adding More Context
We should track more data. What about retry count for failed payments?
Good idea. Let me add that and also track the created timestamp.
flow
machine: @order
scenario: checkout
given:
@customer is logged in
cart has items
$total: number is calculated
$retry_count: number is 0
on> :checkout from @customer
$order_id: string becomes uuid()
$created_at: string becomes now()
emit :payment_request to @payment
with $order_id, $total
order moves to #awaiting_payment
expect:
= order is in #awaiting_payment
= @payment received :payment_request
on :payment_success from @payment
send confirmation email
emit :order_confirmed to @customer
with $order_id
order moves to #confirmed
expect:
= order is in #confirmed
= @customer received :order_confirmed I see $created_at uses `now()` - that's a built-in function?
Yes. EventFlow provides common functions like uuid(), now(), and others. The PHP binding implements them.
The Data Flow with Tables
Can we see what data flows with each event in a clearer way?
Sure. We can use the table format for `with:` to be more explicit about the data structure.
flow
emit :payment_request to @payment
with:
| field | type | value |
| order_id | string | $order_id |
| amount | number | $total |
| created_at | string | $created_at | That's clearer. The table shows exactly what the payment service receives.
I like this for documentation purposes. Non-developers can understand the data contract.
Running Tests Again
Let me run the tests to make sure our changes work.
bash
$ eventflow test order.flow
@order / checkout
happy path
✓ checkout → awaiting_payment (14ms)
✓ payment_success → confirmed (16ms)
2 passing Tests still pass. The happy path is solid.
Now we're ready for edge cases. Jordan and Alex will meet separately to systematically discover the failure scenarios.
I've been noting potential issues as we go. Ready to dig into those.
Session Outcome
What We Refined
- Deep understanding of context variables and types
- Difference between
given:(test setup) and guards (runtime checks) - Data flow through events with
withclause and table format - Added
$retry_countand$created_attracking
Context Variable Operations
| Operation | Syntax | Example |
|---|---|---|
| Set value | becomes | $total becomes 100 |
| Initial value | is | $total: number is 100 |
| Add to number | increases by | $total increases by 25 |
| Subtract | decreases by | $balance decreases by $amount |
| Add to array | adds | $items adds "Laptop" |
| Remove from array | removes | $items removes "Mouse" |
| Empty collection | clears | $cart clears |
Edge Cases Identified (for Session 3)
- Empty cart
- Guest users (not logged in)
- Payment failures
Jordan and Alex will systematically discover and write test scenarios for these in the next session.
The Flow So Far
flow
// order.flow v2
machine: @order
scenario: checkout
given:
@customer is logged in
cart has items
$total: number is calculated
$retry_count: number is 0
on> :checkout from @customer
$order_id: string becomes uuid()
$created_at: string becomes now()
emit :payment_request to @payment
with $order_id, $total
order moves to #awaiting_payment
expect:
= order is in #awaiting_payment
= @payment received :payment_request
on :payment_success from @payment
send confirmation email
emit :order_confirmed to @customer
with $order_id
order moves to #confirmed
expect:
= order is in #confirmed
= @customer received :order_confirmed