Skip to content

Multi-Machine Systems

Machines can work together as a coordinated system. This enables building complex workflows from reusable, composable parts.

Why Machine Systems?

Machines can be used in two ways:

  1. Standalone - A single machine handling a specific domain
  2. System - Multiple machines working together

This enables reusability: a machine developed standalone can be integrated into a larger system.

Single File, Multiple Machines

Define multiple machines in one file for rapid prototyping:

flow
system: e-commerce checkout

machine: @order

  scenario: checkout flow

    on> :checkout from @customer
      ? cart is valid
        emit :payment_request to @payment
        order moves to #awaiting_payment

    on :payment_success from @payment
      order moves to #paid
      emit :reserve_stock to @inventory

    on :stock_reserved from @inventory
      order moves to #fulfilled
      emit :confirmation to @customer

machine: @payment

  scenario: process payment

    on :payment_request from @order
      process card
      ? card is valid
        emit :payment_success to @order
      ?
        emit :payment_failed to @order

machine: @inventory

  scenario: manage stock

    on :reserve_stock from @order
      ? stock is available
        reserve items
        emit :stock_reserved to @order
      ?
        emit :out_of_stock to @order

Separate Files (Reusable Machines)

For reusable machines, use separate files:

order.flow:

flow
machine: @order

scenario: checkout flow

  on> :checkout from @customer
    emit :payment_request to @payment
    order moves to #awaiting_payment

  on :payment_success from @payment
    order moves to #paid

payment.flow:

flow
machine: @payment

scenario: process payment

  on :payment_request from @order
    process card
    ? card is valid
      emit :payment_success to @order

inventory.flow:

flow
machine: @inventory

scenario: manage stock

  on :reserve_stock from @order
    ? stock is available
      reserve items
      emit :stock_reserved to @order

system.flow:

flow
system: e-commerce checkout

uses:
  @order from "./order.flow"
  @payment from "./payment.flow"
  @inventory from "./inventory.flow"

Standalone vs System Usage

A machine can work both standalone and as part of a system:

flow
// Standalone usage - @order works alone
// External events come via API, internal events are mocked

// System usage - @order works with @payment and @inventory
// Events flow between actual machines

This is like gears in a machine - each gear works independently but meshes with others.

The uses Declaration

Import machines into a system:

flow
system: my-application

uses:
  @order from "./order.flow"
  @payment from "./payment.flow"
  @inventory from "./inventory.flow"
  @notification from "./notification.flow"

System Visualization

When you visualize a system, you see all machines and their interactions:

┌─────────────────────────────────────────────────────────────┐
│                   e-commerce checkout                        │
├─────────────┬─────────────┬─────────────┬─────────────┬─────┤
│  @customer  │   @order    │  @payment   │  @inventory │     │
├─────────────┼─────────────┼─────────────┼─────────────┼─────┤
│  :checkout  │             │             │             │     │
│      ───────┼──> receives │             │             │     │
│             │             │             │             │     │
│             │ :pay_request│             │             │     │
│             │      ───────┼──> receives │             │     │
│             │             │             │             │     │
│             │             │ :pay_success│             │     │
│             │  <──────────┼─────────────│             │     │
│             │             │             │             │     │
│             │ :reserve    │             │             │     │
│             │      ───────┼─────────────┼──> receives │     │
│             │             │             │             │     │
│             │             │             │ :reserved   │     │
│             │  <──────────┼─────────────┼─────────────│     │
│             │             │             │             │     │
│ :confirmed  │             │             │             │     │
│  <──────────┼─────────────│             │             │     │
└─────────────┴─────────────┴─────────────┴─────────────┴─────┘

Machine Boundaries

Each machine has clear boundaries:

  • State: Each machine owns its state
  • Context: Each machine owns its variables
  • Events: Machines communicate only through events
  • No shared state: Machines never directly access each other's data
flow
// @order cannot do this:
@payment.$balance    // ❌ Cannot access another machine's context

// @order can only do this:
emit :check_balance to @payment   // ✅ Communicate via events

Composing Systems

Systems can be composed of other systems:

flow
system: full-e-commerce

uses:
  checkout-system from "./checkout/system.flow"
  returns-system from "./returns/system.flow"
  reviews-system from "./reviews/system.flow"

Testing Systems

Test entire systems or individual machines:

bash
# Test individual machine
eventflow test order.flow

# Test entire system
eventflow test system.flow

# Test specific scenario across system
eventflow test system.flow --scenario="complete purchase"

Best Practices

Keep Machines Focused

flow
// Good - focused responsibility
machine: @payment
  // Only handles payment logic

machine: @notification
  // Only handles notifications

// Avoid - doing too much
machine: @order
  // Handles orders, payments, notifications, inventory...

Define Clear Interfaces

Each machine should have clear input/output events:

flow
machine: @payment

// Inputs (events this machine handles)
on :payment_request from @order
on :refund_request from @order

// Outputs (events this machine emits)
// - :payment_success
// - :payment_failed
// - :refund_completed

Use Consistent Event Naming

flow
// Good - consistent patterns
:payment_request / :payment_success / :payment_failed
:stock_request / :stock_reserved / :stock_unavailable

// Avoid - inconsistent
:pay / :payment_ok / :failed

Document Machine Dependencies

flow
system: checkout

// @order depends on:
//   - @payment for payment processing
//   - @inventory for stock management
//   - @notification for customer updates

uses:
  @order from "./order.flow"
  @payment from "./payment.flow"
  @inventory from "./inventory.flow"
  @notification from "./notification.flow"

Released under the MIT License.