Back to Event-Driven Architecture & CQRS
Q5Question 5 of 8

Choreography versus orchestration

Key Concepts
  • Choreography is services reacting to each other's events with no central coordinator; the flow is emergent and distributed.
  • Orchestration is a central coordinator explicitly driving each step and owning the workflow state machine.
  • Choreography gives loose coupling and easy addition of new reactors, but makes observability and compensation hard.
  • Orchestration gives visibility and centralized error handling, but introduces a component every step depends on.
  • Use orchestration for complex, stateful flows with compensations (order sagas, payment flows).
  • Use choreography for simple, independent fan-outs (notifications, analytics, statement updates).
  • A large system commonly uses both: orchestration for the core flow and choreography for independent reactions.

Definition

Choreography is services reacting to each other's events with no central brain; orchestration is a central coordinator explicitly driving each step and owning the workflow.
  • Choreography – each service does its local work, commits, and publishes an event; other services subscribe to events they care about and react, so the workflow is distributed and emergent.
  • Orchestration – a single orchestrator explicitly tells each service what to do and when, owning the workflow state machine and the order of steps including compensations.
  • Choreography strengths – loose coupling, autonomy, easy to add new reactors without changing existing services.
  • Orchestration strengths – full visibility of workflow state, centralized error handling, clean compensation logic.
  • Hybrid – use orchestration for complex compensation-heavy flows and choreography for simple independent fan-outs.

At National Bank of India, choreography is like a dance where there is no director: the order department finishes and shouts "order created," the inventory department hears that and reserves stock and shouts "stock reserved," payment hears that and charges. Everyone just reacts to what they hear. Orchestration is like having a conductor with a baton: the conductor tells the order department "create the order," waits, then tells inventory "reserve stock," then tells payment "charge now," controlling the whole sequence. The dance is more decoupled but nobody has the full picture; the conductor has the full picture but everything flows through them.

Understanding the Concepts

What is choreography?

In choreography, coordination is purely event-driven and decentralized. Each service does its local work, commits, and publishes an event. Other services subscribe and react, publishing their own events in turn. No component knows the whole workflow.

JAVA
package com.bank.order;

import org.springframework.kafka.core.KafkaTemplate;
import org.springframework.stereotype.Service;

@Service
public class OrderService {

    private final KafkaTemplate<String, OrderCreatedEvent> kafka;

    public OrderService(KafkaTemplate<String, OrderCreatedEvent> kafka) {
        this.kafka = kafka;
    }

    public void createOrder(String customerId, String productId) {
        // Create order locally
        String orderId = "ORD-" + System.currentTimeMillis();
        // ... save to database ...

        // Publish event: no idea who will react
        kafka.send("order-events", orderId,
                new OrderCreatedEvent(orderId, customerId, productId));
    }
}
JAVA
package com.bank.inventory;

import org.springframework.kafka.annotation.KafkaListener;
import org.springframework.kafka.core.KafkaTemplate;
import org.springframework.stereotype.Component;

@Component
public class InventoryReactor {

    private final KafkaTemplate<String, StockReservedEvent> kafka;

    public InventoryReactor(KafkaTemplate<String, StockReservedEvent> kafka) {
        this.kafka = kafka;
    }

    @KafkaListener(topics = "order-events", groupId = "inventory-svc")
    public void onOrderCreated(OrderCreatedEvent event) {
        // React: reserve stock
        reserveStock(event.productId(), event.orderId());
        // Publish next event
        kafka.send("inventory-events", event.orderId(),
                new StockReservedEvent(event.orderId(), event.productId()));
    }

    private void reserveStock(String productId, String orderId) {
        // ... reserve logic ...
    }
}
DIAGRAM
// Choreography flow:
// Order Service → publishes OrderCreated
// Inventory Service → hears OrderCreated → publishes StockReserved
// Payment Service → hears StockReserved → publishes PaymentCharged
// Delivery Service → hears PaymentCharged → schedules delivery
// Nobody owns the full flow. Each service knows only its own piece.

What is orchestration?

In orchestration, a central orchestrator owns the workflow as an explicit state machine. It calls each service in turn, tracks the state, and on failure drives compensations in the right order.

JAVA
package com.bank.saga;

import org.springframework.stereotype.Service;
import java.math.BigDecimal;

@Service
public class OrderSagaOrchestrator {

    private final OrderClient orderClient;
    private final InventoryClient inventoryClient;
    private final PaymentClient paymentClient;
    private final SagaStateRepository sagaRepo;

    public OrderSagaOrchestrator(OrderClient orderClient,
                                   InventoryClient inventoryClient,
                                   PaymentClient paymentClient,
                                   SagaStateRepository sagaRepo) {
        this.orderClient = orderClient;
        this.inventoryClient = inventoryClient;
        this.paymentClient = paymentClient;
        this.sagaRepo = sagaRepo;
    }

    public String execute(String customerId, String productId,
                           BigDecimal amount) {
        String sagaId = "SAGA-" + System.currentTimeMillis();

        // Step 1: Create Order
        String orderId = orderClient.createOrder(customerId, productId);
        sagaRepo.updateState(sagaId, "ORDER_CREATED");

        // Step 2: Reserve Stock
        inventoryClient.reserveStock(productId, orderId);
        sagaRepo.updateState(sagaId, "STOCK_RESERVED");

        try {
            // Step 3: Charge Payment
            paymentClient.charge(customerId, amount, orderId);
            sagaRepo.updateState(sagaId, "COMPLETED");
            return orderId;
        } catch (PaymentFailedException e) {
            // Compensate in reverse order from ONE place
            inventoryClient.releaseStock(productId);
            orderClient.cancelOrder(orderId);
            sagaRepo.updateState(sagaId, "COMPENSATED");
            throw new OrderFailedException("Payment failed, compensated");
        }
    }
}
DIAGRAM
// Orchestrator knows the exact state at every point:
// STARTED → ORDER_CREATED → STOCK_RESERVED → COMPLETED
// On failure: STOCK_RESERVED → COMPENSATING → COMPENSATED
// One place to observe, debug, and manage the entire flow

How are choreography and orchestration different? (comparison table)

| Aspect | Choreography | Orchestration |
|---|---|---|
| Coordinator | None | Central orchestrator |
| Flow definition | Implicit (who subscribes to what) | Explicit (state machine) |
| Observability | Hard (trace events across services) | Easy (query orchestrator state) |
| Adding a reactor | Easy (just subscribe, no changes) | Requires orchestrator change |
| Compensation | Each service handles failure events | Orchestrator drives in order |
| Coupling | Services know event types | Services coupled to orchestrator |
| Debugging | Reconstruct from logs | Query single state machine |
| Best for | Simple fan-outs | Complex stateful flows |

When should you use choreography?

Use choreography for simple, independent reactions where adding new consumers should be cheap and the reactions need no coordination.

JAVA
// After a transfer completes, these reactions are independent:
// Notification: send SMS to Priya (independent)
// Statement: append to monthly statement (independent)
// Analytics: update dashboard metrics (independent)
// Fraud: score the transaction (independent)

// Each subscribes to "transfer-events" independently
// Adding a new reactor (e.g., Loyalty Points) = just add a new consumer
// No need to change the Transfer Service or any other consumer

When should you use orchestration?

Use orchestration for complex, ordered, stateful flows with compensations where you need to answer "what state is this in?" and handle failures cleanly.

JAVA
// Order flow needs orchestration because:
// 1. Steps must happen in order (create → reserve → pay → deliver)
// 2. Payment failure requires compensating earlier steps in reverse
// 3. Customer asks "where is my order?" → need to query the state
// 4. Flow is complex enough that implicit event chains become fragile

// Workflow engine options:
// - Spring State Machine (lightweight, in-service)
// - Temporal (durable, long-running, built-in retries)
// - Camunda (BPMN-based, visual workflow designer)

What are common mistakes? (wrong vs correct)

The most common mistake is using choreography for a complex flow that needs compensations, creating the pinball anti-pattern.

JAVA
// WRONG: choreography for a 7-step payment flow with compensations
// Order → Inventory → Fraud → Compliance → Payment → Notification → Delivery
// Events bounce like a pinball with no central tracking
// When Payment fails: who compensates? In what order?
// Answer: scattered across services, implicit, fragile

// CORRECT: orchestrate the core flow, choreograph the side effects
// Orchestrated: Order → Inventory → Payment (with compensations)
// Choreographed: Payment succeeded → Notification, Analytics, Fraud react

What is the hybrid approach?

A large system commonly uses both. Orchestrate the complex, compensation-heavy core flow and choreograph the simple, independent side effects.

JAVA
// Hybrid at National Bank of India:

// ORCHESTRATED (central state machine):
// OrderSagaOrchestrator:
//   T1: Create Order → T2: Reserve Stock → T3: Charge Payment
//   On failure: C2 → C1 (from one place)

// CHOREOGRAPHED (independent event reactions):
// After T3 succeeds, publish PaymentCharged event to Kafka
// Notification Service: sends SMS (independent)
// Statement Service: updates monthly record (independent)
// Loyalty Service: awards points (independent)
// Adding Loyalty Service = just a new Kafka consumer, zero orchestrator changes

What should a senior engineer know?

A senior engineer chooses by the shape of the workflow. Short, simple, independent reactions favor choreography. Complex, ordered, stateful flows with compensations and observable status favor orchestration. They use a workflow engine like Temporal or Camunda for the orchestrated parts to get durable state, retries, timeouts, and visibility out of the box.

JAVA
// Decision checklist:
// 1. Does the flow need compensations? → Orchestrate
// 2. Must you answer "what state is this order in?" → Orchestrate
// 3. Are reactions independent with no coordination? → Choreograph
// 4. Should adding a new reactor be zero-change? → Choreograph
// 5. Is the flow long-running (hours/days)? → Orchestrate with Temporal
// 6. Avoid: god orchestrator (business logic in coordinator)
// 7. Avoid: pinball architecture (complex flow as chained events)

Visual Overview

DIAGRAM
  Choreography (No Coordinator)
  ─────────────────────────────
  Order Svc ──event──▶ Inventory Svc ──event──▶ Payment Svc
                                                     │
                                                     │ (if fails)
                                                     ▼
                              ◀──failure event── Payment Svc
  Order Svc ◀──failure event── Inventory Svc
  (each reacts independently, no central view)


  Orchestration (Central Coordinator)
  ────────────────────────────────────
  Orchestrator
      │── command ──▶ Order Svc
      │◀── result ──│
      │── command ──▶ Inventory Svc
      │◀── result ──│
      │── command ──▶ Payment Svc
      │◀── FAILED ──│
      │── compensate ──▶ Inventory Svc (release)
      │── compensate ──▶ Order Svc (cancel)
      │ State: COMPENSATED (one place, full visibility)


  Hybrid (Best of Both)
  ─────────────────────
  Orchestrator ──▶ Order ──▶ Inventory ──▶ Payment
      │                                       │
      │ (orchestrated core flow)               │── event ──▶ Kafka
      │                                       │              │
      │                                       │         ┌────┤────┐
      │                                       │     Notify  Audit  Loyalty
      │                                       │   (choreographed fan-out)

Key Takeaway

Choreography and orchestration are the two ways to coordinate a multi-service workflow, and the difference is whether there is a central brain. In choreography, each service reacts to events from other services with no coordinator, so the flow is emergent and distributed. In orchestration, a central component owns the workflow as an explicit state machine and tells each service what to do and when.

Choreography shines when the reactions are simple, independent, and coordination-free. At National Bank of India, after a transfer succeeds, the notification, statement, and fraud services each subscribe to the transfer event and react independently. Adding a new reactor, like a loyalty points service, is just a new Kafka consumer with no changes to any existing service. But when Pankaj tried using choreography for the full order-payment flow with compensations, it became a tangled web where nobody could answer "what state is this order in?" and the compensation logic was scattered across services.

Orchestration solves that by putting a state machine in charge. The orchestrator knows the exact state of every workflow instance, drives compensations in the correct reverse order from one place, and makes the flow observable and testable as a unit. The cost is a component that every step depends on, so it must be highly available and must never become a god orchestrator stuffed with business logic.

Remember one sentence: orchestrate complex, stateful flows with compensations for visibility and clean error handling, and choreograph simple, independent fan-outs for loose coupling and easy extensibility.

---

Share
Use ← → arrow keys to navigate