A saga is a sequence of local transactions. Each local transaction updates the database and publishes a message or event to trigger the next local transaction in the saga. If a local transaction fails, the saga executes a series of compensating transactions that undo the changes made by the preceding local transactions.

Let’s see a saga in action for a simple e-commerce order. Imagine a customer places an order. This involves several steps:

  1. Create Order: A new order is created in the OrderService. This is a local transaction.
  2. Reserve Credit: The PaymentService reserves the credit for the order amount. This is another local transaction.
  3. Update Inventory: The InventoryService decrements the stock for the ordered items. This is a third local transaction.
  4. Approve Order: If all previous steps succeed, the OrderService marks the order as approved. This is the final local transaction.

Here’s how a saga orchestrates this:

Orchestration-based Saga:

The OrderService acts as the orchestrator. It initiates the saga and sends commands to other services.

  • Client: Sends "Place Order" request to OrderService.
  • OrderService:
    • Starts saga.
    • Performs local transaction: INSERT INTO orders (order_id, customer_id, status) VALUES (123, 456, 'PENDING');
    • Publishes OrderCreated event.
  • PaymentService:
    • Listens for OrderCreated event.
    • Performs local transaction: INSERT INTO payments (order_id, amount, status) VALUES (123, 100.00, 'RESERVED');
    • Publishes PaymentReserved event.
  • InventoryService:
    • Listens for PaymentReserved event.
    • Performs local transaction: UPDATE inventory SET quantity = quantity - 1 WHERE item_id = 'ABC';
    • Publishes InventoryUpdated event.
  • OrderService:
    • Listens for InventoryUpdated event.
    • Performs local transaction: UPDATE orders SET status = 'APPROVED' WHERE order_id = 123;
    • Saga completes successfully.

Now, what if InventoryService fails?

  • InventoryService:
    • Fails to update inventory (e.g., out of stock).
    • Publishes InventoryUpdateFailed event.
  • OrderService:
    • Listens for InventoryUpdateFailed event.
    • Initiates compensating transactions.
    • Performs compensating local transaction: UPDATE orders SET status = 'CANCELLED' WHERE order_id = 123;
    • Publishes OrderCancelled event.
  • PaymentService:
    • Listens for OrderCancelled event.
    • Performs compensating local transaction: UPDATE payments SET status = 'VOIDED' WHERE order_id = 123;
    • Saga completes with rollback.

The problem sagas solve is managing consistency across multiple independent microservices without resorting to distributed locking or two-phase commit (2PC), which are notoriously difficult to implement and scale in a distributed environment. Each service maintains its own ACID-compliant database, and the saga coordinates them through a series of reliable messages.

The key levers you control are the local transaction logic within each service and the event publishing/consuming mechanism. You define the sequence of operations, the events that signal completion or failure, and the compensating actions for each step. This approach gives you eventual consistency, meaning that while the system might be temporarily inconsistent during the saga, it will eventually reach a consistent state.

The most surprising thing is that a saga doesn’t guarantee atomicity in the traditional sense. It’s designed to achieve semantic atomicity – the business process as a whole either succeeds or fails gracefully. If a compensating transaction itself fails, you’re in a more complex recovery scenario, often requiring manual intervention or a higher-level retry mechanism, which is why the design of robust compensating actions is paramount.

The next problem you’ll run into is handling complex conditional logic within a saga or managing sagas that span many services, often leading to the exploration of choreography-based sagas.

Want structured learning?

Take the full Microservices course →