Skip to content

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.
@customer@order@payment1:checkout2generate $order_id:payment_request($order_id, $total)3(processes)4:payment_success5send confirmation email6:order_confirmed
Here's the flow:
  1. Customer sends :checkout to order
  2. Order generates $order_id, sends :payment_request to payment
  3. Payment processes (that's the external service)
  4. Payment sends :payment_success back to order
  5. Order sends confirmation email
  6. 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 with clause and table format
  • Added $retry_count and $created_at tracking

Context Variable Operations

OperationSyntaxExample
Set valuebecomes$total becomes 100
Initial valueis$total: number is 100
Add to numberincreases by$total increases by 25
Subtractdecreases by$balance decreases by $amount
Add to arrayadds$items adds "Laptop"
Remove from arrayremoves$items removes "Mouse"
Empty collectionclears$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

Released under the MIT License.