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:
- Standalone - A single machine handling a specific domain
- 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:
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 @orderSeparate Files (Reusable Machines)
For reusable machines, use separate files:
order.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 #paidpayment.flow:
machine: @payment
scenario: process payment
on :payment_request from @order
process card
? card is valid
emit :payment_success to @orderinventory.flow:
machine: @inventory
scenario: manage stock
on :reserve_stock from @order
? stock is available
reserve items
emit :stock_reserved to @ordersystem.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:
// 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 machinesThis is like gears in a machine - each gear works independently but meshes with others.
The uses Declaration
Import machines into a system:
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
// @order cannot do this:
@payment.$balance // ❌ Cannot access another machine's context
// @order can only do this:
emit :check_balance to @payment // ✅ Communicate via eventsComposing Systems
Systems can be composed of other systems:
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:
# 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
// 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:
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_completedUse Consistent Event Naming
// Good - consistent patterns
:payment_request / :payment_success / :payment_failed
:stock_request / :stock_reserved / :stock_unavailable
// Avoid - inconsistent
:pay / :payment_ok / :failedDocument Machine Dependencies
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"