Actor Communication
EventFlow is built on the Actor Model. Understanding actor communication patterns is key to designing effective systems.
Actor Model Basics
In the Actor Model:
- Each actor (machine) has isolated state
- Actors communicate only through messages (events)
- Messages are processed one at a time
- Message delivery is asynchronous
No Shared State
Actors never directly access each other's state:
// ❌ Not possible - cannot read another machine's context
? @payment.$balance > 100
process order
// ✅ Correct - ask through events
emit :check_balance to @payment
on :balance_sufficient from @payment
process order
on :balance_insufficient from @payment
reject orderAsynchronous Communication
Events are asynchronous - the sender doesn't wait:
on> :checkout from @customer
emit :payment_request to @payment // Sent, not waiting
order moves to #awaiting_payment // Continues immediately
// Later, when payment responds...
on :payment_success from @payment
order moves to #paidCommunication Patterns
Request-Response
The most common pattern - one machine asks, another answers:
machine: @order
on> :checkout from @customer
emit :authorize_payment to @payment
order moves to #authorizing
on :payment_authorized from @payment
order moves to #paid
on :payment_declined from @payment
order moves to #payment_failed
machine: @payment
on :authorize_payment from @order
validate card
? card is valid
emit :payment_authorized to @order
?
emit :payment_declined to @orderFire and Forget
Send an event without expecting a response:
on> :order_completed from @system
emit :log_event to @analytics // No response expected
emit :send_email to @notification // No response expected
order moves to #completedScatter-Gather (Fan-Out/Fan-In)
Send to multiple machines, collect responses:
on> :validate_order from @customer
// Fan-out
emit :check_payment to @payment
emit :check_inventory to @inventory
emit :check_fraud to @fraud
order moves to #validating
// Fan-in
on :payment_ok from @payment
$payment_valid becomes true
check_all_validations
on :inventory_ok from @inventory
$inventory_valid becomes true
check_all_validations
on :fraud_ok from @fraud
$fraud_valid becomes true
check_all_validations
// Helper check
? $payment_valid and $inventory_valid and $fraud_valid
order moves to #validatedChain (Pipeline)
Events flow through a sequence of machines:
// @order -> @payment -> @fraud -> @inventory -> @shipping
machine: @order
on> :checkout from @customer
emit :process to @payment
machine: @payment
on :process from @order
charge card
emit :verify to @fraud
machine: @fraud
on :verify from @payment
check fraud signals
emit :reserve to @inventory
machine: @inventory
on :reserve from @fraud
reserve stock
emit :ship to @shipping
machine: @shipping
on :ship from @inventory
create shipment
emit :order_shipped to @orderSaga Pattern
Coordinated transactions with compensation:
machine: @order_saga
on> :start_checkout from @customer
emit :reserve_inventory to @inventory
saga moves to #reserving
on :inventory_reserved from @inventory
emit :charge_payment to @payment
saga moves to #charging
on :payment_charged from @payment
emit :confirm_order to @order
emit :order_confirmed to @customer
saga moves to #completed
// Compensation on failure
on :payment_failed from @payment
emit :release_inventory to @inventory
emit :checkout_failed to @customer
saga moves to #failed
on :inventory_unavailable from @inventory
emit :checkout_failed to @customer
saga moves to #failedEvent Routing
By Source
Handle the same event differently based on sender:
on :approval from @manager
order moves to #manager_approved
on :approval from @director
order moves to #director_approved
skip_further_approvalsWithout Source (Any Sender)
Accept from any machine:
on :status_update
update internal status
log status changeTimeouts and Retries
EventFlow doesn't have built-in timeout syntax, but you can design timeout handling:
machine: @order
on> :checkout from @customer
emit :payment_request to @payment
$payment_requested_at becomes now
order moves to #awaiting_payment
on :payment_success from @payment
order moves to #paid
on :payment_timeout
? order is in #awaiting_payment
$retry_count increases by 1
? $retry_count < 3
emit :payment_request to @payment
?
order moves to #payment_failed
emit :checkout_failed to @customer
// Separate scheduled check
on :check_payment_timeouts
triggered: every 5 minutes
for each order in #awaiting_payment
? $payment_requested_at older than 10 minutes
emit :payment_timeout to @orderError Handling
Explicit Error Events
machine: @payment
on :payment_request from @order
? card is valid
process payment
? payment successful
emit :payment_success to @order
?
emit :payment_error to @order
with $error_code, $error_message
?
emit :invalid_card to @orderHandling Errors
machine: @order
on :payment_success from @payment
order moves to #paid
on :payment_error from @payment
log error $error_message
? $retry_count < 3
$retry_count increases by 1
emit :payment_request to @payment
?
order moves to #payment_failed
emit :order_failed to @customer
on :invalid_card from @payment
emit :update_payment_method to @customer
order moves to #needs_payment_methodBest Practices
Define Clear Contracts
Document what events each machine sends and receives:
machine: @payment
// RECEIVES:
// :payment_request from @order
// - order_id: string
// - amount: number
// - currency: string
//
// EMITS:
// :payment_success to @order
// - transaction_id: string
// :payment_failed to @order
// - error_code: string
// - error_message: stringKeep Messages Small
Only include necessary data:
// Good - minimal data
emit :order_completed to @notification
with $order_id, $customer_email
// Avoid - too much data
emit :order_completed to @notification
with $entire_order_object, $all_customer_data, $full_historyUse Idempotent Handlers
Design event handlers to be safely retried:
on :process_payment from @order
? not already_processed($transaction_id)
process the payment
mark as processed
// If already processed, do nothing