Coordinator Pattern
The coordinator pattern describes any architecture where a central node is responsible for sequencing operations across multiple participants. Common examples: Saga Orchestration, Two-Phase Commit (2PC), and Raft Leader-based consensus.
The rule
All edges go between the coordinator and individual services. Services are NOT connected to each other directly.
If Service A needs to trigger Service B as part of a saga step, the coordinator issues both calls — first to A, then (after A responds) to B. Services never call each other.
CORRECT: Coordinator → Service A → Coordinator → Service B
WRONG: Coordinator → Service A → Service B
(Service A calls Service B directly)Saga Orchestration
In an orchestrated saga, the saga coordinator drives each step: it calls Service A, waits for a reply, then calls Service B, and so on. On failure, it issues compensating transactions in reverse order.
Topology
const topology = new TopologyBuilder(true);
const orchestratorGroup = topology.createGroup('orchestrator', {
label: 'Saga Orchestrator',
});
orchestratorGroup
.addProcess({ id: 'saga', label: 'Order Saga', state: 'running' })
.autoResize();
const servicesGroup = topology.createGroup('services', { label: 'Domain Services' });
servicesGroup
.addProcess({ id: 'orders', label: 'Order Service', state: 'running' })
.addProcess({ id: 'payments', label: 'Payment Service', state: 'running' })
.addProcess({ id: 'inventory', label: 'Inventory Service', state: 'running' })
.addProcess({ id: 'shipping', label: 'Shipping Service', state: 'running' })
.autoResize();
// All edges are between saga coordinator and services
// Services are NOT connected to each other
topology.connect('saga', 'orders', { protocol: 'http', label: 'create order' });
topology.connect('saga', 'payments', { protocol: 'http', label: 'charge card' });
topology.connect('saga', 'inventory', { protocol: 'http', label: 'reserve items' });
topology.connect('saga', 'shipping', { protocol: 'http', label: 'schedule delivery' });Animation — happy path
const flow = new FlowBuilder();
flow.scenario('happy-path', 'Happy Path', 'All saga steps succeed');
flow
.from('saga').to('orders')
.showMessage('[STEP 1] Create order record')
.from('orders').to('saga')
.showMessage('[OK] Order #42 created')
.from('saga').to('inventory')
.showMessage('[STEP 2] Reserve 3x Widget Pro')
.from('inventory').to('saga')
.showMessage('[OK] Items reserved (SKU-7)')
.from('saga').to('payments')
.showMessage('[STEP 3] Charge $149.97 to card ending 4242')
.from('payments').to('saga')
.showMessage('[OK] Payment authorized (txn: ABC123)')
.from('saga').to('shipping')
.showMessage('[STEP 4] Schedule delivery for 2026-04-20')
.from('shipping').to('saga')
.showMessage('[OK] Shipment #SH-9 scheduled')
.showMessage('[SAGA COMPLETE] Order #42 fully processed');Animation — compensating transaction
flow.scenario('compensation', 'Payment Failure', 'Card declined — compensate previous steps');
flow
.from('saga').to('orders')
.showMessage('[STEP 1] Create order record')
.from('orders').to('saga')
.showMessage('[OK] Order #43 created')
.from('saga').to('inventory')
.showMessage('[STEP 2] Reserve items')
.from('inventory').to('saga')
.showMessage('[OK] Items reserved')
.from('saga').to('payments')
.showMessage('[STEP 3] Charge card ...')
.from('payments').to('saga')
.showError('[DECLINED] Insufficient funds')
// Compensating transactions in reverse order
.from('saga').to('inventory')
.showMessage('[COMPENSATE] Release reserved items')
.from('inventory').to('saga')
.showMessage('[OK] Reservation cancelled')
.from('saga').to('orders')
.showMessage('[COMPENSATE] Cancel order record')
.from('orders').to('saga')
.showMessage('[OK] Order #43 cancelled')
.showError('[SAGA FAILED] Order rolled back — payment declined');
return flow;Two-Phase Commit (2PC)
In 2PC, a coordinator sends a Prepare to all participants (Phase 1), collects votes, then sends Commit or Abort to all (Phase 2).
Topology
const topology = new TopologyBuilder(true);
const coordinatorGroup = topology.createGroup('2pc', { label: '2PC Coordinator' });
coordinatorGroup
.addProcess({ id: 'coord', label: 'Transaction Coordinator', state: 'running' })
.autoResize();
const participantsGroup = topology.createGroup('participants', { label: 'Participants' });
participantsGroup
.addProcess({ id: 'db-a', label: 'Database A', shape: 'cylinder', state: 'running' })
.addProcess({ id: 'db-b', label: 'Database B', shape: 'cylinder', state: 'running' })
.addProcess({ id: 'db-c', label: 'Database C', shape: 'cylinder', state: 'running' })
.autoResize();
// Coordinator ↔ each participant
topology.connect('coord', 'db-a', { protocol: 'tcp', label: '2PC' });
topology.connect('coord', 'db-b', { protocol: 'tcp', label: '2PC' });
topology.connect('coord', 'db-c', { protocol: 'tcp', label: '2PC' });Animation
const flow = new FlowBuilder();
flow.scenario('commit', 'Successful 2PC', 'All participants vote YES');
flow
.parallel([
['coord', 'db-a'],
['coord', 'db-b'],
['coord', 'db-c'],
])
.showMessage('[PHASE 1] PREPARE sent to all participants')
.parallel([
['db-a', 'coord'],
['db-b', 'coord'],
['db-c', 'coord'],
])
.showMessage('[PHASE 1] All participants voted YES')
.parallel([
['coord', 'db-a'],
['coord', 'db-b'],
['coord', 'db-c'],
])
.showMessage('[PHASE 2] COMMIT sent to all participants')
.parallel([
['db-a', 'coord'],
['db-b', 'coord'],
['db-c', 'coord'],
])
.showMessage('[PHASE 2] All participants confirmed COMMIT')
.showMessage('[OK] Distributed transaction committed');
return flow;Raft Leader
In Raft, the elected leader receives all client writes, appends them to its log, and replicates to followers before acknowledging.
Topology
const topology = new TopologyBuilder(true);
const raftGroup = topology.createGroup('raft', { label: 'Raft Cluster' });
raftGroup
.addProcess({ id: 'leader', label: 'Leader (Node 1)', state: 'running' })
.addProcess({ id: 'follower-1', label: 'Follower (Node 2)', state: 'running' })
.addProcess({ id: 'follower-2', label: 'Follower (Node 3)', state: 'running' })
.autoResize();
// All replication is leader → follower (followers do NOT talk to each other)
topology.connect('leader', 'follower-1', { protocol: 'tcp', label: 'AppendEntries' });
topology.connect('leader', 'follower-2', { protocol: 'tcp', label: 'AppendEntries' });
// Client writes go to leader only
topology.connect('client', 'leader', { protocol: 'tcp', label: 'write' });In Raft, followers never communicate directly — they only receive AppendEntries RPCs from the leader. This maps directly to the coordinator pattern: the leader is the coordinator, followers are participants.