Orchestration and Choreography Patterns

πŸ“– 6 min read

These patterns define how multiple services coordinate to complete complex business processes, either through centralized control (orchestration) or distributed coordination (choreography).

Orchestration Pattern

Centralized orchestration provides visibility and control at the cost of creating a coordination bottleneck.

Centralized approach where a single orchestrator service controls and coordinates the execution of multiple services in a specific sequence.

Use When:

  • Need centralized control over business processes
  • Complex workflows with multiple steps
  • Require transaction-like behavior across services
  • Need detailed workflow monitoring and error handling

Considerations:

  • Orchestrator can become a single point of failure
  • Risk of creating a distributed monolith
  • May introduce performance bottlenecks

Example: Order fulfillment process where an orchestrator coordinates payment processing, inventory reservation, shipping arrangement, and customer notification in sequence.

Order Orchestrator:
  1. Call Payment Service β†’ Wait for response
  2. If success, call Inventory Service β†’ Wait for response
  3. If success, call Shipping Service β†’ Wait for response
  4. If success, call Notification Service
  5. If any fail, execute compensation logic
Implementation: AWS Step Functions Temporal Camunda Custom orchestrator

Choreography Pattern

Distributed approach where each service knows when to act and what to do without central coordination, typically through event-driven communication.

Use When:

  • Services can operate independently
  • Don’t need strict workflow control
  • Want maximum decoupling between services
  • Building event-driven architectures

Considerations:

  • Difficult to monitor and debug workflows
  • Complex error handling and compensation
  • No central view of the business process

Example: E-commerce system where placing an order triggers events that cause inventory, payment, and shipping services to act independently without central coordination.

Order Service β†’ OrderCreated event β†’ Event Bus
                                         ↓
          β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
          ↓              ↓              ↓              ↓
    Inventory      Payment         Shipping      Notification
    Service        Service         Service        Service

Implementation: Event brokers (Kafka, RabbitMQ, AWS EventBridge)


Saga Pattern

Pattern introduced by Hector Garcia-Molina and Kenneth Salem (1987), popularized for microservices by Chris Richardson and others

A saga breaks a distributed transaction into a sequence of local transactions. Each service performs its local transaction and publishes an event or calls the next step. If any step fails, the saga executes compensating transactions in reverse order to undo the changes already made.

The Problem Sagas Solve:

In a monolith, a single database transaction can span multiple operations atomically. In microservices with separate databases, you can’t use a single transaction. Traditional distributed transactions (2PC/XA) are slow, don’t scale, and many databases don’t support them.

Monolith (single transaction):          Microservices (no shared transaction):
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”     β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ BEGIN TRANSACTION               β”‚     β”‚ Order       β”‚  β”‚ Payment     β”‚  β”‚ Inventory   β”‚
β”‚   INSERT order                  β”‚     β”‚ Service     β”‚  β”‚ Service     β”‚  β”‚ Service     β”‚
β”‚   UPDATE inventory              β”‚     β”‚ (own DB)    β”‚  β”‚ (own DB)    β”‚  β”‚ (own DB)    β”‚
β”‚   INSERT payment                β”‚     β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
β”‚ COMMIT (all or nothing)         β”‚           β”‚               β”‚               β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜           β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                                              How do we make these consistent?

How a Saga Works:

Happy Path (all steps succeed):

Step 1              Step 2              Step 3              Result
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”       β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”       β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”       β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Create   │──────→│ Reserve  │──────→│ Charge   │──────→│ Complete β”‚
β”‚ Order    β”‚       β”‚ Inventoryβ”‚       β”‚ Payment  β”‚       β”‚ Order    β”‚
β”‚ (pending)β”‚       β”‚          β”‚       β”‚          β”‚       β”‚ (confirm)β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜       β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜       β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜       β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
    T1                 T2                 T3

Failure Path (step 3 fails, compensate in reverse):

Step 1              Step 2              Step 3 FAILS
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”       β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”       β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Create   │──────→│ Reserve  │──────→│ Charge   β”‚ βœ— Payment declined
β”‚ Order    β”‚       β”‚ Inventoryβ”‚       β”‚ Payment  β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜       β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜       β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                        β”‚                   β”‚
                        β”‚    Compensate     β”‚
                        β”‚β†β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                        ↓
                   β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”       β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
                   β”‚ Release  │←──────│ Cancel   β”‚
                   β”‚ Inventoryβ”‚       β”‚ Order    β”‚
                   β”‚ (C2)     β”‚       β”‚ (C1)     β”‚
                   β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜       β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Compensating Transactions:

A compensating transaction undoes the effect of a previous step. It’s not always a simple rollbackβ€”it’s a semantic undo that makes business sense.

Original Transaction Compensating Transaction Why Not Simple Rollback?
Create order Cancel order Order ID already assigned, must mark cancelled not delete
Reserve inventory Release inventory Other orders may have reserved same items since
Charge payment Refund payment Can’t undo a charge; must issue separate refund
Send email Send correction email Can’t unsend; must send follow-up
Example: Order saga with compensation

T1: CreateOrder(items, customer)     β†’ C1: CancelOrder(orderId)
T2: ReserveInventory(items)          β†’ C2: ReleaseInventory(items)
T3: ChargePayment(customer, amount)  β†’ C3: RefundPayment(customer, amount)
T4: ShipOrder(orderId)               β†’ C4: ??? (can't unship!)

Note: Some actions can't be compensated (shipping). These are called
"pivot transactions" - once executed, the saga must complete forward.

Use When:

  • Need consistency across multiple services with separate databases
  • Cannot use distributed transactions (2PC/XA)
  • Business processes span multiple services
  • You can define compensating actions for each step

Implementation Approaches:

Orchestration-based Saga: A central orchestrator tells each service what to do and handles failures.

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                      Saga Orchestrator                          β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”   β”‚
β”‚  β”‚ Saga State: { orderId: 123, step: "PAYMENT", status: OK }β”‚   β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜   β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
         β”‚              β”‚              β”‚              β”‚
         β–Ό              β–Ό              β–Ό              β–Ό
    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”   β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”   β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”   β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”
    β”‚ Order   β”‚   β”‚Inventoryβ”‚   β”‚ Payment β”‚   β”‚Shipping β”‚
    β”‚ Service β”‚   β”‚ Service β”‚   β”‚ Service β”‚   β”‚ Service β”‚
    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜   β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜   β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜   β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Orchestrator:
  1. Call OrderService.create() β†’ OK, orderId=123
  2. Call InventoryService.reserve(123) β†’ OK
  3. Call PaymentService.charge(123) β†’ FAILED
  4. Call InventoryService.release(123) β†’ OK (compensate)
  5. Call OrderService.cancel(123) β†’ OK (compensate)
  6. Return failure to client

Choreography-based Saga: Services react to events and publish their own events. No central coordinator.

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”  OrderCreated  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β” InventoryReserved β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Order   │───────────────→│Inventory│──────────────────→│ Payment β”‚
β”‚ Service β”‚                β”‚ Service β”‚                   β”‚ Service β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜                β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜                   β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
     ↑                          ↑                             β”‚
     β”‚                          β”‚                             β”‚
     β”‚    OrderCancelled        β”‚    InventoryReleased        β”‚ PaymentFailed
     β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Each service:
  1. Listens for events it cares about
  2. Performs its local transaction
  3. Publishes result event
  4. If it receives a failure event, publishes compensation event
Approach Pros Cons
Orchestration Easy to understand workflow, centralized error handling, clear saga state Orchestrator is coupling point, can become bottleneck
Choreography Loose coupling, no single point of failure, services are independent Hard to understand full workflow, difficult to debug, no central view

Handling Failures in Compensations:

What if a compensating transaction fails? You can’t compensate a compensation.

Saga failure during compensation:

T1 βœ“ β†’ T2 βœ“ β†’ T3 βœ— β†’ C2 βœ— (compensation failed!)

Options:
1. Retry C2 with backoff (compensations should be idempotent)
2. Log for manual intervention
3. Forward recovery: try to complete saga anyway if possible

Best practice: Make compensations idempotent and retriable
  ReleaseInventory(orderId) should succeed even if called twice

Saga Limitations

No isolation: Other transactions can see intermediate states (order exists but payment pending). Use semantic locks or "pending" states to handle this.

Complexity: N steps means N compensating transactions to implement and test.

Eventual consistency: System is temporarily inconsistent during saga execution.


Choosing Between Patterns

Use orchestration when you need visibility into complex workflows and centralized error handling. Use choreography when service independence and loose coupling are more important than workflow visibility.

Quick Reference

Pattern Comparison

Pattern Control Coupling Monitoring Use Case
Orchestration Centralized Medium-High Easy Complex workflows, strict control
Choreography Distributed Low Difficult Independent services, loose coupling
Saga Either Varies Medium Distributed transactions

Decision Framework

Question Pattern
Need strict workflow control? Orchestration
Want loose coupling? Choreography
Need distributed transactions? Saga (choose orchestration or choreography based on other needs)

Trade-offs

Orchestration

Pros:

  • Easy to monitor workflows
  • Clear workflow visualization
  • Centralized error handling

Cons:

  • Single point of failure
  • Potential bottleneck

Choreography

Pros:

  • Loose coupling
  • No single point of failure
  • High scalability

Cons:

  • Hard to monitor
  • Complex debugging
  • No workflow visibility

Saga Pattern

Pros: Maintains consistency without distributed transactions

Cons: Complexity of compensating actions, eventual consistency


Found this guide helpful? Share it with your team:

Share on LinkedIn