🏗️ Section 17 · Question #2

What is the Difference Between Choreography and Orchestration in Saga

In distributed systems, the Saga Pattern replaces classical two-phase commit transactions with a sequence of decoupled local transactions paired with compensating actions upon f...

In distributed systems, the Saga Pattern replaces classical two-phase commit transactions with a sequence of decoupled local transactions paired with compensating actions upon failure. Coordinating these distributed execution steps is achieved through two fundamentally distinct paradigms: Choreography and Orchestration.


🟢 Junior Level

Conceptual Foundation: Two Ways to Coordinate a Saga

Choreography (Event-Driven):
[Order Service] ──OrderCreated──► [Payment Service] ──PaymentCompleted──► [Delivery Service]
     ▲                                   │
     └────────PaymentFailed (Rollback)───┘

Orchestration (Command-Driven):
                   ┌───────────────────────────┐
                   │     Saga Orchestrator     │
                   │ (State Machine + Saga Log)│
                   └──────┬───────┬───────┬────┘
           1. CreateOrder │       │ 2. Charge │ 3. Dispatch
                          ▼       ▼           ▼
                   [Order Service] [Payment] [Delivery]

Core Metaphor:

  • Choreography resembles a ballroom dance: there is no conductor on stage. Each dancer has memorized their steps and responds dynamically to the gestures and movements of their partners. Services exchange domain events (OrderCreated, PaymentAuthorized) via a message broker.
  • Orchestration resembles a symphony orchestra with a conductor: the musicians do not watch each other; they watch the conductor (orchestrator), who explicitly commands who plays, what note to strike, and when (ReserveInventoryCommand, ChargeCardCommand).

3 Key Distinctions:

  1. Coordination Authority: Choreography is decentralized (no central coordinator); Orchestration is centralized in a dedicated Saga Execution Coordinator (SEC).
  2. Messaging Semantics: Choreography relies on Domain Events (facts about what has happened); Orchestration relies on targeted Commands (directives on what must be done) and command replies.
  3. Application Scope: Choreography excels in concise pipelines (2–3 services). Orchestration is essential for complex business processes (4+ services, branching conditions, timers, manual interventions).

🟡 Middle Level

1. Choreography Architecture (Event-Driven)

Services react to domain events published by peers, execute a local transaction in their private database, and publish follow-up events via the Transactional Outbox pattern:

// Payment Service reacts to order creation event
@Component
public class PaymentEventHandler {

    private final PaymentService paymentService;
    private final EventPublisher eventPublisher;

    @KafkaListener(topics = "order-events", groupId = "payment-group")
    public void onOrderCreated(OrderCreatedEvent event) {
        try {
            paymentService.processPayment(event.orderId(), event.amount());
            // Success: Publish event for downstream consumers (Delivery Service)
            eventPublisher.publish(new PaymentSucceededEvent(event.orderId()));
        } catch (InsufficientFundsException e) {
            // Failure: Publish compensating event for upstream consumers
            eventPublisher.publish(new PaymentFailedEvent(event.orderId(), e.getMessage()));
        }
    }
}

Rollback Flow: If step 3 (Delivery) encounters an error, it publishes DeliveryFailedEvent. The PaymentService must subscribe to this event and issue a refund (refund()), while OrderService must subscribe and transition the order status to CANCELLED.


2. Orchestration Architecture (Command-Driven)

A central Saga Execution Coordinator (SEC) persists state machine transitions to a durable Saga Log and dispatches point-to-point commands to participants:

@Component
public class OrderSagaOrchestrator {

    private final SagaRepository sagaRepo;
    private final CommandGateway commandGateway;

    @Transactional
    public void onEvent(SagaReplyEvent reply) {
        OrderSagaData saga = sagaRepo.findById(reply.sagaId())
            .orElseThrow();

        switch (saga.getState()) {
            case PAYMENT_PENDING -> {
                if (reply.isSuccess()) {
                    saga.transitionTo(SagaState.DELIVERY_PENDING);
                    commandGateway.send(new ScheduleDeliveryCommand(saga.getOrderId()));
                } else {
                    saga.transitionTo(SagaState.COMPENSATING_ORDER);
                    commandGateway.send(new CancelOrderCommand(saga.getOrderId()));
                }
            }
            case DELIVERY_PENDING -> {
                if (reply.isSuccess()) {
                    saga.transitionTo(SagaState.COMPLETED);
                } else {
                    saga.transitionTo(SagaState.REFUNDING_PAYMENT);
                    commandGateway.send(new RefundPaymentCommand(saga.getOrderId()));
                }
            }
            case REFUNDING_PAYMENT -> {
                saga.transitionTo(SagaState.COMPENSATING_ORDER);
                commandGateway.send(new CancelOrderCommand(saga.getOrderId()));
            }
            case COMPENSATING_ORDER -> saga.transitionTo(SagaState.REJECTED);
        }
        sagaRepo.save(saga);
    }
}

Detailed Comparison Matrix

Architectural Criterion Choreography Orchestration
Communication Style Event-Driven (Pub/Sub: “Order Placed”) Command-Driven (Point-to-Point: “Debit Funds”)
Coordination Distributed; embedded in each participant’s logic Centralized in Saga Execution Coordinator (SEC)
Coupling Loose invocation coupling, but high event semantic coupling Centralized workflow coupling; participants remain decoupled from each other
Observability Low: requires aggregating logs or Distributed Tracing (Zipkin/Jaeger) High: complete execution history visible in the central Saga Log
Cyclic Dependencies High risk of unintended circular event storms ($A \to B \to C \to A$) Eliminated: workflow is modeled as a Directed Acyclic Graph (DAG)
Single Point of Failure (SPOF) No central coordinating service Orchestrator is critical (requires clustering and replicated storage)
Workflow Evolution Adding steps requires reconfiguring subscriptions across multiple services Adding steps requires modifying the workflow definition in the orchestrator

🔴 Senior Level

1. Architectural Hazards: Pinball Spaghetti vs. The God Orchestrator

Choreography Hazard: The “Spaghetti Architecture” Pinball Trap

As the system expands beyond 5–10 services, choreography degenerates into the “pinball machine” anti-pattern: domain events bounce unpredictably between microservices. Modifying the schema of a single event (OrderCreatedEvent) breaks multiple downstream subscribers. Circular dependency cycles emerge, turning microservices into a tangled, distributed monolith.

Orchestration Hazard: Smart Orchestrator, Anemic Microservices

The most prevalent orchestration failure is absorbing domain business logic into the orchestrator. Microservices degenerate into passive CRUD workers, while the orchestrator becomes a bloated “God Object” that must be recompiled and redeployed whenever discounting formulas or validation rules change. Rule of Thumb: The orchestrator manages workflow routing, state transitions, and timeouts only. Domain decisions (e.g., how inventory is allocated or discounts calculated) remain strictly within individual domain services.


2. Message Racing & Out-of-Order Delivery

In asynchronous choreography, message delivery order is not guaranteed:

  • A downstream delivery service may receive OrderCancelledEvent (compensation) before the delayed OrderCreatedEvent arrives due to network latency or Kafka consumer partition rebalancing.
  • Solution — State-Based Idempotency: If a cancellation arrives for an unknown entity, the service persists a tombstone record: Order { id: 123, status: CANCELLED_PREMATURELY }. When the delayed CreateOrder command subsequently arrives, the service inspects the state, recognizes the cancellation tombstone, and discards the creation.
Race Condition Sequence:
1. [Broker] ────OrderCancelled──► [Delivery] (Created with status CANCELLED_PREMATURELY)
2. [Broker] ────CreateDelivery──► [Delivery] (Cancellation detected ──► Skipped!)

3. Production Durable Workflow Engines

Writing home-grown orchestrators using Java switch-case constructs is prone to state corruption during ungraceful process termination. Production-grade systems rely on dedicated durable execution engines:

  1. Temporal.io / Cadence:
    • Code-as-Configuration: Sagas are written as standard, deterministic Java code (Workflow.sleep(), loops, try-catch).
    • Powered by Event Sourcing: The engine intercepts activity calls, logs execution history, and deterministically reconstructs thread state line-by-line upon node failure.
  2. Camunda 7 / 8 (Zeebe):
    • BPMN 2.0 Workflows: Business processes are modeled visually with native support for Compensation Boundary Events, SLA timers, and human escalation tasks.
    • Zeebe partitions event streams via Raft consensus, delivering high throughput.
  3. Axon Framework:
    • Provides native @Saga components with declarative key-association indexing (associationProperty = "orderId").

4. Pragmatic Hybrid Architecture

Mature enterprise systems employ a hybrid model:

  • Orchestration inside Bounded Contexts: Within an Order Management domain, complex multi-step checkouts (credit validation, inventory reservations, fraud analysis) are orchestrated via an internal SEC.
  • Choreography across Bounded Contexts: Upon saga completion, the Order domain publishes a public domain event (OrderConfirmedEvent), which external bounded contexts (Analytics, Notifications, Recommendations) consume independently.

4 Tricky Questions

1. What happens if the orchestrator node crashes (kill -9) midway through an executing Saga?

Answer:

  1. A properly designed orchestrator persists state transitions to a durable Saga Log prior to dispatching commands (or relies on an Event Sourcing engine like Temporal).
  2. When the failed node restarts (or a surviving cluster replica assumes partition ownership):
    • It reloads the last confirmed state from the Saga Log.
    • It assesses the status of in-flight commands via polling or timeout expiration.
    • It re-emits pending commands. Because downstream participant services must be strictly idempotent, duplicate commands do not corrupt business data.
  3. Without a persistent Saga Log, a crashed orchestrator leaves the distributed transaction permanently stranded in an inconsistent state.

2. Why are step timeouts (SLAs) and escalation significantly harder to implement in Choreography than in Orchestration?

Answer:

  1. In Orchestration, timeout management is centralized: the orchestrator arms a durable timer (e.g., Temporal’s Workflow.await(Duration.ofMinutes(5)) or a database scheduler). If a response is not received before deadline expiration, it automatically triggers compensating transactions.
  2. In Choreography, there is no centralized owner of workflow time:
    • Either every participating service must implement complex custom timeout schedulers and emit StepTimeoutEvents.
    • Or an auxiliary monitoring service must be built to track SLAs across topics, which essentially recreates a fragile, decentralized orchestrator.

3. What is a “Phantom Compensation” and how do you protect against it?

Answer:

  1. A Phantom Compensation occurs when a compensating action executes for an operation that was never actually applied on the target service.
  2. Example: An orchestrator issues ReserveInventory, but the request drops in network transit. The orchestrator times out and sends ReleaseInventory. The inventory service receives ReleaseInventory, increments stock by +1, and later receives the delayed original ReserveInventory command.
  3. Protection:
    • Enforce globally unique SagaId and TransactionId tokens across all steps.
    • Verify state preconditions: a compensation can only execute if the forward action was applied. If received prior to the forward action, the record is flagged as CANCELLED, suppressing subsequent execution of the forward action.

4. How does network latency compare between Choreography and Orchestration across a 4-step linear saga?

Answer:

  1. In Choreography, messages travel point-to-point in a direct chain: $A \to B \to C \to D$. Total latency equals: \(T_{total} = T_{A} + RTT(A \to B) + T_{B} + RTT(B \to C) + T_{C} + RTT(C \to D) + T_{D}\)
  2. In Orchestration, each step requires two-way communication with the coordinator: $O \to A \to O \to B \to O \to C \to O \to D \to O$. Round-trip hops double: \(RTT_{total} = 2 \times N \times RTT\)
  3. However, Orchestration enables parallel step execution (e.g., running fraud detection and inventory reservation concurrently), whereas Choreography requires complex Fan-Out/Fan-In event aggregation topologies to synchronize parallel steps.

🎯 Interview Cheat Sheet

30-Second Elevator Pitch

“Choreography and Orchestration are the two coordination strategies for Sagas. Choreography is decentralized and event-driven: services publish and consume domain events over Kafka without a central coordinator, offering loose invocation coupling for simple 2–3 service flows. Orchestration is centralized and command-driven: a dedicated coordinator or durable workflow engine (Temporal, Camunda) manages an explicit state machine and Saga Log. Orchestration is the industry standard for enterprise business processes (4+ services) because it avoids ‘spaghetti’ event loops, provides end-to-end visibility, handles timeouts cleanly, and enables parallel step execution.”


Empirical Decision Rule

  • Up to 3 services with simple linear flow $\to$ Choreography.
  • 4+ services, complex branching, parallel steps, strict SLA timeouts, or regulatory audit requirements $\to$ Orchestration (Temporal, Camunda, Zeebe).
  • Enterprise Multi-Domain Systems $\to$ Hybrid (Orchestration inside Bounded Contexts, Choreography between Bounded Contexts).

Red Flags (What NOT to Say)

  • ❌ “An orchestrator is just a Spring @Service with a try-catch wrapping synchronous REST calls.” — A synchronous REST orchestrator without a persistent Saga Log loses all transaction state upon crashing.
  • ❌ “In choreography, services are completely decoupled and have zero knowledge of each other.” — Services are tightly coupled to the semantics, payload schemas, and timing of peer events.
  • ❌ “Orchestration is an anti-pattern because microservices must always be fully decentralized.” — Extreme decentralization in complex workflows causes catastrophic event pinballing; orchestration is the established enterprise standard for multi-step sagas.