Skip to content

Events Overview

EventFlow follows the Actor Model: every machine is an actor that communicates through asynchronous events. There is no direct method calling - only event emission and handling.

Core Concepts

Machine = Actor = Always listening for eventsEvent arrivesProcessBack to listening

Machines are isolated units that:

  • Have their own state
  • React to incoming events
  • Emit events to other machines
  • Never directly access another machine's state

API Events vs Internal Events

API Events (on ... (api))

Externally accessible events that can be triggered via HTTP API or external systems:

flow
on :checkout from @customer (api)
  // This can be triggered via API
  order moves to #awaiting_payment

The (api) suffix marks this event as a public endpoint.

Internal Events (on)

System-internal events that can only be triggered by other machines:

flow
on :payment_success from @payment
  // Only triggered by @payment machine
  order moves to #paid

No (api) suffix - this event is internal to the system.

Receiving Events

Use on :event from @actor for internal events or on :event from @actor (api) for external:

flow
machine: @order

scenario: checkout flow

  // Public endpoint
  on :checkout from @customer (api)
    ? cart is valid
      emit :payment_request to @payment
      order moves to #awaiting_payment

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

  on :payment_failed from @payment
    order moves to #failed
    emit :checkout_failed to @customer

The from Clause

The from clause filters who can send the event:

flow
// Accept from specific actor
on :payment_success from @payment
  order moves to #paid

// Accept from any actor (no from clause)
on :status_update
  update internal status

// Different handling based on source
on :approval from @manager
  order moves to #manager_approved

on :approval from @director
  order moves to #director_approved

Sending Events

Use emit :event to @actor:

flow
on :checkout from @customer (api)
  ? cart is valid
    emit :payment_request to @payment
    order moves to #awaiting_payment

Events with Data

Events can carry data using the with: table:

flow
on :add_item from @customer (api)
  $items adds $item
  $total increases by $item.price
  emit :item_added to @customer with:
    | field | value  |
    | item  | $item  |
    | total | $total |

Simple inline format for few fields:

flow
emit :payment_request to @payment
  with $order_id, $total, $currency

Events with Data Validation

Add a validation column to the with: table to define validation rules:

flow
emit :checkout to @order with:
  | field    | value     | validation                       |
  | order_id | $order_id | required, string, valid uuid     |
  | email    | $email    | required, string, valid email    |
  | total    | $total    | required, number, greater than 0 |
  | coupon   | $coupon   | optional, string                 |

Validation rules are comma-separated. Type rules (string, number) are validation rules like any other.

When validation fails, a :validation_failed event is automatically emitted with error details:

flow
on :validation_failed
  emit :checkout_error to @customer with:
    | field  | value   | validation |
    | errors | $errors | required   |

See Data Validation for complete validation documentation.

Event Routing and Instances

Events are routed to specific machine instances based on the conversation context.

First Event Creates Instance

When the first event in a conversation arrives, EventFlow creates a new instance (aggregate):

flow
// First :checkout creates @order:abc123
on :checkout from @customer (api)
  order moves to #processing

At this point:

  • A new instance @order:abc123 is created
  • A conversation context is established
  • The instance has its own isolated state and context

Subsequent Events Route to Existing Instance

After the first event, all related events in the same conversation are automatically routed to the same instance:

flow
// Routes to existing @order:abc123 via conversation context
on :payment_success from @payment
  order moves to #paid

EventFlow handles this automatically - no correlation IDs needed in your code.

How Routing Works

@customer triggers :checkoutEventFlow creates @order:abc123Conversation context established@payment sends :payment_success→ routes to @order:abc123

Multiple Instances

Different customers create different instances, each processing independently:

@order:abc123← Customer A@order:xyz789← Customer B (parallel)@order:def456← Customer C (parallel)

Each instance maintains its own state and context - they never interfere with each other.

See Conversations for details on conversation context. See Instance Management for instance lifecycle details.

Pure Async Model

There is no request-response pattern. Communication is always:

  1. emit - send event, don't wait
  2. on - handle event when it arrives
flow
machine: @order

scenario: payment flow

  on :checkout from @customer (api)
    emit :payment_request to @payment
    order moves to #awaiting_payment
    // doesn't wait for response

  on :payment_success from @payment
    // handles response when it arrives
    order moves to #paid

Note: While machine-to-machine communication is purely async via events, API callers can receive structured HTTP responses via the reply keyword. See Machine Responses for details on returning data to external callers.

Execution Model

Event handlers execute synchronously - all actions within a handler complete as a single unit.

Event emission is asynchronous - emit queues the event and continues without waiting for the target machine to process it.

flow
on :checkout from @customer (api)
  // ─── Runs synchronously (single transaction) ───
  ? cart is valid
    $order_id becomes uuid()
    order moves to #awaiting_payment

  // ─── Queued for async processing ───
  emit :payment_request to @payment
  emit :analytics_event to @analytics

This follows the Actor Model: actors process messages independently and communicate through asynchronous message passing.

Event Flow Diagram

@customer@order@payment:checkout:payment_request(processes):payment_success:order_confirmed

Event Patterns

Request-Response Pattern (via Events)

flow
machine: @order

  on :checkout from @customer (api)
    emit :payment_request to @payment
    order moves to #awaiting_payment

  on :payment_success from @payment
    order moves to #paid

  on :payment_failed from @payment
    order moves to #payment_failed

Fan-Out Pattern

flow
on :order_completed from @system (api)
  emit :update_inventory to @inventory
  emit :send_confirmation to @notification
  emit :record_sale to @analytics
  emit :update_loyalty to @customer

Fan-In Pattern

flow
on :checkout from @customer (api)
  emit :check_payment to @payment
  emit :check_fraud to @fraud
  emit :check_inventory to @inventory

on :payment_ok from @payment
  $payment_ready becomes true
  ? all_checks_passed
    order moves to #confirmed

on :fraud_ok from @fraud
  $fraud_ready becomes true
  ? all_checks_passed
    order moves to #confirmed

on :inventory_ok from @inventory
  $inventory_ready becomes true
  ? all_checks_passed
    order moves to #confirmed

Saga Pattern

flow
machine: @order_saga

  on :start_order from @customer (api)
    emit :reserve_inventory to @inventory
    saga moves to #reserving_inventory

  on :inventory_reserved from @inventory
    emit :process_payment to @payment
    saga moves to #processing_payment

  on :payment_success from @payment
    emit :confirm_inventory to @inventory
    emit :order_confirmed to @customer
    saga moves to #completed

  // Compensating actions
  on :payment_failed from @payment
    emit :release_inventory to @inventory
    emit :order_failed to @customer
    saga moves to #failed

Best Practices

Use Descriptive Event Names

flow
// Good - clear intent
emit :payment_authorization_requested to @payment
emit :order_shipment_prepared to @warehouse

// Avoid - vague
emit :request to @payment
emit :update to @warehouse

Handle All Responses

flow
on :checkout from @customer (api)
  emit :payment_request to @payment
  order moves to #awaiting_payment

on :payment_success from @payment
  order moves to #paid

on :payment_failed from @payment
  order moves to #payment_failed

on :payment_timeout from @payment
  order moves to #payment_timeout

Document Event Data

flow
emit :order_details to @warehouse
  with:
    | field       | type   | value       |
    | order_id    | string | $order_id   |
    | items       | array  | $items      |
    | priority    | string | "standard"  |
    | ship_by     | date   | $ship_date  |

Broadcasting Events

To send an event to all instances of a machine at once, use the broadcast syntax:

flow
emit :reminder to all @order in #pending
  with $deadline

Broadcasts are useful for:

  • Sending notifications to all relevant instances
  • Synchronizing multiple machines
  • Triggering periodic checks across instances

See Broadcasting Events for full documentation on broadcast syntax, state filtering, and best practices.

Released under the MIT License.