Skip to content

Instance Management

Machine vs Instance

In EventFlow, it's essential to understand the distinction between a machine and an instance:

ConceptWhat It IsExample
MachineA template - the behavior definition in your .flow filemachine: @order
InstanceA running entity with its own state and data (also called aggregate)@order:abc123

When you write machine: @order, you're defining a template. At runtime, many instances can exist:

@order (machine/template)
  ├── @order:abc123 (instance) → state: #processing
  ├── @order:xyz789 (instance) → state: #paid
  └── @order:def456 (instance) → state: #shipped

Key Insight

You define behavior once in your .flow file. The runtime creates and manages potentially thousands of independent instances, each with its own state and context.

Event-Sourced Architecture

EventFlow follows an event-sourced architecture where each machine instance (aggregate) maintains its state through a sequence of events. This page explains how instances are created, identified, and managed.

Event Sourcing Foundation

Every machine instance is an aggregate root in event sourcing terms:

Event Storeaggregate_idevent_typedataseqorder-123:checkout{customer}1order-123:payment_success{txn_id}2order-456:checkout{customer}1order-123:shipped{tracking}3

When an event arrives:

  1. Identify the target aggregate (by aggregate_id)
  2. Load all previous events for that aggregate
  3. Replay events to rebuild current state
  4. Process the new event
  5. Persist the new event to the store

Aggregate Lifecycle

Creating a New Aggregate

The first event in a machine's flow creates a new aggregate instance. The system automatically generates a unique aggregate ID:

flow
machine: @order

scenario: order lifecycle

  on :checkout from @customer (api)        // First event → creates NEW aggregate
    // aggregate_id auto-generated: "order-abc123"
    order moves to #pending
    emit :payment_request to @payment

When :checkout arrives:

  • No existing aggregate reference → System generates aggregate_id
  • New aggregate created with state #pending
  • Event stored: {aggregate_id: "order-abc123", event: ":checkout", ...}

INFO

The (api) suffix indicates this is an API/public endpoint (externally accessible). Whether it creates a new aggregate depends on whether it's the first event in the machine's flow, not on the (api) suffix itself.

Subsequent Events

Events after the first one target an existing aggregate and require a valid reference:

flow
on :payment_success from @payment   // Requires EXISTING aggregate
  order moves to #paid

on :cancel_order from @customer (api)    // API endpoint, but requires existing aggregate
  order moves to #cancelled

If a subsequent event arrives without a valid aggregate reference (via conversation context or explicit ID), the runtime throws an error.

Aggregate Reference

How does :payment_success know which order instance to update?

Answer: Through Conversation Context - see the Conversations page for details.

Event Routing Summary

The (api) suffix and aggregate creation are independent concepts:

  • (api) suffix = API/public endpoint (externally accessible)
  • Aggregate creation = determined by event position in flow (first event creates, subsequent events require reference)
Event TypeAPI Endpoint?Creates Aggregate?Routing
on :event (api) (first in flow)YesYes (new)New aggregate_id generated
on :event (api) (subsequent)YesNoRequires aggregate reference, else error
on :event (internal)NoDepends on contextConversation context → correct aggregate

Example

flow
machine: @order

scenario: checkout flow

  // First event → creates new aggregate (order-abc123)
  // Also an API endpoint (externally callable)
  on :checkout from @customer (api)
    order moves to #awaiting_payment
    emit :payment_request to @payment

  // Internal event → requires existing aggregate
  // Routed via conversation context
  on :payment_success from @payment
    order moves to #paid

  // API endpoint but NOT first event
  // Requires aggregate reference in payload, else runtime error
  on :cancel_order from @customer (api)
    ? order is in #awaiting_payment
      order moves to #cancelled

Runtime Error for Missing Reference

When a non-first event arrives without a valid aggregate reference:

EventFlowError: Cannot route event ':cancel_order' to @order
  - No aggregate_id provided in event payload
  - No conversation context available
  Hint: This event requires an existing order aggregate.
        Include 'order_id' in your request or use conversation context.

Instance Isolation

Each instance has its own:

  • State - current machine state
  • Context - all $variables
  • Event history - all events that have been processed
@order:abc123 → state: #paid, $total: 1200, $items: ["Laptop"]
@order:xyz789 → state: #pending, $total: 50, $items: ["Mouse"]

Instances never share state. They only communicate through events.

State Reconstruction

Because all events are stored, state can be reconstructed at any point:

Events for order-abc123:
  1. :checkout        → state becomes #pending
  2. :payment_request → (emit, no state change)
  3. :payment_success → state becomes #paid
  4. :shipped         → state becomes #shipped

Current state: #shipped

This enables:

  • Time-travel debugging - replay events to any point
  • Audit trails - complete history of all changes
  • State recovery - rebuild from events after failure

Multiple Instances

A machine definition is a template. Many instances can exist:

@order (machine definition)
  ├── @order:abc123 (instance 1)
  ├── @order:xyz789 (instance 2)
  ├── @order:def456 (instance 3)
  └── ... (any number of instances)

Each instance:

  • Processes its own events
  • Has its own state and context
  • Is identified by a unique aggregate_id

Released under the MIT License.