Saga choreography can feel like a jazz improvisation where each musician reacts to the others, while orchestration is more like a conductor leading an orchestra.

Let’s watch choreography in action. Imagine a simple e-commerce order flow:

  1. Order Service: Receives a new order.

    • POST /orders with payload: {"customerId": 123, "items": [...], "totalAmount": 150.00}
    • Publishes an OrderCreated event to a message broker (e.g., Kafka, RabbitMQ).
  2. Inventory Service: Subscribes to OrderCreated events.

    • Receives OrderCreated.
    • Checks inventory for items. If available:
      • Deducts items from stock.
      • Publishes an ItemsReserved event.
    • If inventory is insufficient:
      • Publishes an InventoryUnavailable event.
  3. Payment Service: Subscribes to ItemsReserved events.

    • Receives ItemsReserved.
    • Processes payment. If successful:
      • Publishes a PaymentProcessed event.
    • If payment fails:
      • Publishes a PaymentFailed event.
  4. Order Service (again): Subscribes to PaymentProcessed, PaymentFailed, and InventoryUnavailable events.

    • If PaymentProcessed is received: Updates order status to "PAID".
    • If PaymentFailed or InventoryUnavailable is received: Updates order status to "FAILED" and publishes an OrderFailed event.
  5. Notification Service: Subscribes to OrderFailed and events indicating success (e.g., OrderPaid).

    • Sends an email/SMS to the customer.

In this choreography, each service listens for events from other services and reacts independently. There’s no central brain dictating the flow. The "logic" is distributed.

Now, consider orchestration. The same e-commerce order flow:

  1. Order Service: Receives a new order.

    • POST /orders with payload: {"customerId": 123, "items": [...], "totalAmount": 150.00}
    • Initiates a saga orchestration.
  2. Orchestrator (e.g., a dedicated service or workflow engine like Camunda, Temporal):

    • Receives the StartOrderSaga command from the Order Service.
    • Sends a ReserveItems command to the Inventory Service.
    • Waits for a response from the Inventory Service (e.g., ItemsReserved or InventoryUnavailable).
    • If ItemsReserved: Sends a ProcessPayment command to the Payment Service.
    • Waits for a response from the Payment Service (e.g., PaymentProcessed or PaymentFailed).
    • If PaymentProcessed: Sends an UpdateOrderStatus command to the Order Service (with status "PAID") and a SendOrderConfirmation command to the Notification Service.
    • If InventoryUnavailable or PaymentFailed: Sends an UpdateOrderStatus command to the Order Service (with status "FAILED") and a SendOrderFailureNotification command to the Notification Service.

In orchestration, the orchestrator explicitly tells each participant service what to do and when. It maintains the state of the saga.

The core problem both patterns solve is managing distributed transactions across multiple services without relying on traditional two-phase commit (2PC), which doesn’t scale in microservices. They allow services to remain independent while still ensuring eventual consistency for complex business processes. The "saga" is the sequence of local transactions, where each local transaction updates the state and triggers the next step. If a step fails, compensating transactions are executed to undo previous steps.

The most surprising thing about saga choreography is how quickly it devolves into spaghetti code if you’re not extremely disciplined about event naming and domain boundaries. Because every service is reacting to every other service, a small change in one service’s event can ripple through many others, and debugging becomes a nightmare of tracing event chains across logs. You end up with implicit dependencies that are hard to visualize and manage.

The key levers you control in choreography are the events themselves: their names, their schemas, and the topics they are published to. In orchestration, you control the workflow definition (e.g., a BPMN diagram or a code-based workflow) and the commands sent between services.

The next concept to explore is how to handle compensating transactions effectively in both patterns, especially when a service that needs to compensate has already failed.

Want structured learning?

Take the full Saga-pattern course →