1. What Problem Does Saga Solve?
Imagine an e-commerce application with these microservices:
E-Commerce Application
|
+-------------------+-------------------+
| | |
Order Service Payment Service Inventory Service
| | |
+-------------------+-------------------+
|
Shipping ServiceA customer places an order.
The business process might be:
Create Order
↓
Reserve Inventory
↓
Process Payment
↓
Create Shipment
↓
Order CompletedThe problem is that each operation belongs to a different database.
For example:
Order Service → OrderDB
Inventory Service → InventoryDB
Payment Service → PaymentDB
Shipping Service → ShippingDBWe cannot normally use a single SQL transaction such as:
BEGIN TRANSACTION
OrderDB
InventoryDB
PaymentDB
ShippingDB
COMMITbecause these are independent microservices.
This is where Saga Pattern comes in.
2. What Is Saga Design Pattern?
A Saga is a sequence of local transactions where each microservice performs its own transaction.
If one transaction fails, previously completed transactions are compensated by executing corresponding compensating transactions.
Conceptually:
Transaction 1
↓
Transaction 2
↓
Transaction 3
↓
Transaction 4If Transaction 3 fails:
Transaction 1 ✓
Transaction 2 ✓
Transaction 3 ✗
↓
Compensation 2
↓
Compensation 1So instead of a traditional distributed ACID transaction, Saga provides eventual consistency using local transactions + compensation.
3. E-Commerce Example
Suppose customer places an order:
Order #1001
Product: Laptop
Quantity: 1
Price: ₹80,000The workflow is:
Customer
|
↓
Order Service
|
↓
Inventory Service
|
↓
Payment Service
|
↓
Shipping Service
|
↓
Order CompletedLet's define the transactions.
T1 – Create Order
Order Service:
Order Status = PendingT2 – Reserve Inventory
Inventory Service:
Laptop Stock
100 → 99T3 – Process Payment
Payment Service:
₹80,000 chargedT4 – Create Shipment
Shipping Service:
Shipment CreatedFinally:
Order Status = Confirmed4. What Happens If Payment Fails?
Suppose:
T1 Create Order ✓
T2 Reserve Inventory ✓
T3 Payment ✗We cannot simply rollback T1 and T2 using a normal database rollback because they happened in different databases.
Instead:
Payment Failed
↓
Release Inventory
↓
Cancel OrderSo:
T1 Create Order ✓
↓
T2 Reserve Stock ✓
↓
T3 Payment ✗
↓
C2 Release Stock ✓
↓
C1 Cancel Order ✓This is the core concept of Saga.
5. Saga Has Two Main Approaches
There are two major implementations.
Approach 1 – Choreography
Services communicate through events.
Order Service
|
OrderCreated
↓
Inventory Service
|
InventoryReserved
↓
Payment Service
|
PaymentCompleted
↓
Shipping ServiceThere is no central coordinator.
Approach 2 – Orchestration
A central Saga Orchestrator controls the workflow.
Saga Orchestrator
|
+-------------+-------------+
| | |
↓ ↓ ↓
Order Inventory Payment
Service Service Service
|
↓
ShippingThe orchestrator says:
Reserve inventorythen:
Process paymentthen:
Create shipmentIf something fails:
Release inventory
Cancel orderFor an enterprise e-commerce application, orchestration is often easier to understand and manage, especially when the workflow becomes complex.
6. Which One Should We Use?
| Feature | Choreography | Orchestration |
|---|---|---|
| Central controller | No | Yes |
| Simple workflows | Excellent | Good |
| Complex workflows | Difficult | Excellent |
| Debugging | Difficult | Easier |
| Business workflow visibility | Lower | Higher |
| Coupling | Event-based | Orchestrator-based |
| Failure handling | Distributed | Centralized |
| Large enterprise workflows | Can become complicated | Often preferable |
For the example below, I'll use Saga Orchestration.
7. Overall Architecture
Let's design the system.
Client
|
↓
API Gateway
|
↓
Order Service
|
↓
Saga Orchestrator
|
+----------------+----------------+
| | |
↓ ↓ ↓
Inventory Payment Shipping
Service Service Service
| | |
InventoryDB PaymentDB ShippingDBCommunication could use:
Azure Service Bus
Kafka
RabbitMQFor an Azure-based .NET system, Azure Service Bus is a natural choice.
8. Database Design
An important Saga principle is:
Each microservice owns its own database.
Don't do this:
Order Service
|
↓
Shared Database
↑
|
Inventory ServiceInstead:
Order Service
↓
OrderDB
Inventory Service
↓
InventoryDB
Payment Service
↓
PaymentDB
Shipping Service
↓
ShippingDB9. Order Model
Order Service might have:
public class Order
{
public Guid Id { get; set; }
public Guid CustomerId { get; set; }
public decimal TotalAmount { get; set; }
public OrderStatus Status { get; set; }
public DateTime CreatedAt { get; set; }
}Status:
public enum OrderStatus
{
Pending,
InventoryReserved,
PaymentProcessing,
Confirmed,
Failed,
Cancelled
}10. Order Items
public class OrderItem
{
public Guid Id { get; set; }
public Guid OrderId { get; set; }
public Guid ProductId { get; set; }
public int Quantity { get; set; }
public decimal Price { get; set; }
}11. Inventory Model
Inventory Service owns:
public class Inventory
{
public Guid ProductId { get; set; }
public int AvailableQuantity { get; set; }
public int ReservedQuantity { get; set; }
}Example:
Product Available Reserved
Laptop 100 0After reservation:
Laptop 99 112. Inventory Reservation Model
We should maintain a separate reservation record.
public class InventoryReservation
{
public Guid Id { get; set; }
public Guid OrderId { get; set; }
public Guid ProductId { get; set; }
public int Quantity { get; set; }
public ReservationStatus Status { get; set; }
}Status:
public enum ReservationStatus
{
Reserved,
Released
}Why?
Because Saga requires us to know:
What exactly should I compensate?
13. Payment Model
Payment Service:
public class Payment
{
public Guid Id { get; set; }
public Guid OrderId { get; set; }
public decimal Amount { get; set; }
public PaymentStatus Status { get; set; }
public string TransactionReference { get; set; }
}Status:
public enum PaymentStatus
{
Pending,
Completed,
Failed,
Refunded
}14. Shipment Model
Shipping Service:
public class Shipment
{
public Guid Id { get; set; }
public Guid OrderId { get; set; }
public string Address { get; set; }
public ShipmentStatus Status { get; set; }
}15. Saga State Model
The orchestrator should maintain Saga state.
For example:
public class OrderSaga
{
public Guid SagaId { get; set; }
public Guid OrderId { get; set; }
public SagaStatus Status { get; set; }
public bool OrderCreated { get; set; }
public bool InventoryReserved { get; set; }
public bool PaymentCompleted { get; set; }
public bool ShipmentCreated { get; set; }
public DateTime CreatedAt { get; set; }
public DateTime UpdatedAt { get; set; }
}This becomes very useful for:
monitoring
retries
recovery
debugging
compensation
16. Complete Saga Flow
Let's look at the complete process.
Customer
|
| Place Order
↓
Order Service
|
| Order Created
↓
Saga Orchestrator
|
| Reserve Inventory
↓
Inventory Service
|
| Inventory Reserved
↓
Saga Orchestrator
|
| Process Payment
↓
Payment Service
|
| Payment Completed
↓
Saga Orchestrator
|
| Create Shipment
↓
Shipping Service
|
| Shipment Created
↓
Saga Orchestrator
|
↓
Order Confirmed17. Step 1 – Customer Creates Order
Client:
POST /api/ordersRequest:
{
"customerId": "C001",
"items": [
{
"productId": "P100",
"quantity": 1
}
]
}Order Service creates:
OrderId = 1001
Status = PendingDatabase:
Orders
1001 | C001 | 80000 | PendingThen publish:
OrderCreatedEvent:
public record OrderCreatedEvent(
Guid OrderId,
Guid CustomerId,
decimal Amount);18. Step 2 – Saga Starts
The orchestrator receives:
OrderCreatedIt creates:
SagaId = S1001
OrderId = 1001
Status = StartedThen sends:
ReserveInventory19. Step 3 – Inventory Reservation
Inventory Service receives:
{
"sagaId": "S1001",
"orderId": "1001",
"productId": "P100",
"quantity": 1
}It executes a local database transaction.
For example:
BEGIN TRANSACTION
Check available stock
Available = Available - 1
Create Reservation
COMMITDatabase becomes:
Available = 99
Reserved = 1Then publish:
InventoryReserved20. Step 4 – Payment
Saga orchestrator receives:
InventoryReservedThen sends:
ProcessPaymentPayment Service:
BEGIN TRANSACTION
Create Payment
Status = Processing
Call payment provider
Payment successful
Status = Completed
COMMITThen:
PaymentCompleted21. Step 5 – Shipping
Saga receives:
PaymentCompletedThen:
CreateShipmentShipping Service creates:
ShipmentId = SH1001
OrderId = 1001
Status = CreatedThen publishes:
ShipmentCreated22. Step 6 – Complete Saga
Saga Orchestrator receives:
ShipmentCreatedNow:
OrderCreated ✓
InventoryReserved ✓
PaymentCompleted ✓
ShipmentCreated ✓So:
Saga Status = CompletedAnd Order Service is instructed:
ConfirmOrderOrder:
1001 | Confirmed23. What Happens When Payment Fails?
This is where Saga becomes interesting.
Suppose:
Order Created ✓
Inventory Reserved ✓
Payment ✗The orchestrator receives:
PaymentFailedIt knows:
InventoryReserved = true
PaymentCompleted = falseTherefore compensation is required.
24. Compensation Flow
The orchestrator sends:
ReleaseInventoryInventory Service:
BEGIN TRANSACTION
Available = Available + 1
Reservation.Status = Released
COMMITNow:
Available = 100
Reserved = 0Then orchestrator sends:
CancelOrderOrder Service:
Order.Status = CancelledFinal state:
Order = Cancelled
Inventory = Released
Payment = Failed
Saga = Compensated25. Important Point – Compensation Is NOT Rollback
This is one of the most important interview concepts.
Traditional transaction:
BEGIN
Operation A
Operation B
Operation C
ROLLBACKSaga:
Operation A
Operation B
Operation C → FAILED
Compensation B
Compensation AThere is no global database rollback.
Instead:
A compensating transaction semantically reverses the business effect of a previous transaction.
26. Example: Payment Succeeds but Shipping Fails
Consider:
Order ✓
Inventory ✓
Payment ✓
Shipping ✗Now we need to compensate.
Possible sequence:
Shipping Failed
↓
Refund Payment
↓
Release Inventory
↓
Cancel OrderSo:
T1 Create Order ✓
T2 Reserve Inventory ✓
T3 Payment ✓
T4 Shipping ✗
C3 Refund Payment ✓
C2 Release Inventory ✓
C1 Cancel Order ✓Final:
Order = Cancelled
Inventory = Released
Payment = Refunded
Shipping = Failed27. What If Compensation Also Fails?
This is a very important real-world scenario.
Suppose:
Payment Failed
↓
Release Inventory
↓
Inventory Service FAILEDNow:
Order = Pending
Inventory = Reserved
Payment = FailedWe cannot simply give up.
The Saga must retry the compensation.
ReleaseInventory
↓
FAILED
↓
Retry
↓
FAILED
↓
Retry
↓
SUCCESSTherefore Saga implementations need:
Retry
Dead-letter queue
Idempotency
Timeout
Monitoring
Manual recovery
28. Retry Strategy
For example:
Attempt 1
↓
5 seconds
↓
Attempt 2
↓
30 seconds
↓
Attempt 3
↓
5 minutesThis is called exponential backoff.
For Azure Service Bus, failed messages can eventually be moved to a dead-letter queue.
29. Idempotency Is Extremely Important
Suppose:
ReserveInventorymessage is delivered twice.
Without idempotency:
Message 1 → Reserve 1 item
Message 2 → Reserve another itemIncorrect:
Stock: 100 → 98But we wanted:
Stock: 100 → 99Therefore every command should have a unique identifier.
Example:
public class ProcessedMessage
{
public Guid MessageId { get; set; }
public DateTime ProcessedAt { get; set; }
}Before processing:
Has MessageId already been processed?If yes:
IgnoreOtherwise:
Process
Save MessageId30. Better Idempotency Model
Instead of only MessageId, use a business operation ID.
Example:
SagaId = S1001
OrderId = 1001
Operation = ReserveInventoryCreate unique constraint:
(SagaId, Operation)Then duplicate commands cannot create duplicate reservations.
31. Transactional Outbox Pattern
There is another major problem.
Suppose Order Service does:
BEGIN TRANSACTION
Insert Order
COMMITThen:
Publish OrderCreatedWhat if the application crashes between these operations?
Database Insert ✓
Publish Event ✗Now the order exists but Saga never receives the event.
This is where Transactional Outbox Pattern is commonly combined with Saga.
32. Outbox Table
Order Service database:
Orders
OutboxMessagesWhen creating the order:
BEGIN TRANSACTION
INSERT INTO Orders
INSERT INTO OutboxMessages
COMMITBoth happen in the same local database transaction.
Example:
Orders
OrderId = 1001
Status = PendingAnd:
OutboxMessages
MessageId = M1001
Type = OrderCreated
Payload = {...}
Published = falseA background publisher then reads:
Published = falseand sends the message.
After successful publishing:
Published = trueThis greatly improves reliability.
33. Saga + Outbox Architecture
A robust architecture becomes:
Saga Orchestrator
|
Message Broker
|
+---------------------+---------------------+
| | |
↓ ↓ ↓
Order Service Inventory Service Payment
| | |
OrderDB InventoryDB PaymentDB
| | |
Outbox Outbox Outbox
| | |
+---------------------+---------------------+
|
Azure Service Bus34. Commands vs Events
This distinction is important.
Command
A command tells another service:
Do something.
Examples:
CreateOrder
ReserveInventory
ProcessPayment
CreateShipment
RefundPayment
ReleaseInventory
CancelOrderEvent
An event says:
Something happened.
Examples:
OrderCreated
InventoryReserved
InventoryReservationFailed
PaymentCompleted
PaymentFailed
ShipmentCreated
ShipmentFailed35. Example Command
public record ReserveInventoryCommand(
Guid MessageId,
Guid SagaId,
Guid OrderId,
Guid ProductId,
int Quantity);36. Example Event
public record InventoryReservedEvent(
Guid MessageId,
Guid SagaId,
Guid OrderId);Failure:
public record InventoryReservationFailedEvent(
Guid MessageId,
Guid SagaId,
Guid OrderId,
string Reason);37. Saga State Machine
The orchestrator can be modeled as a state machine.
OrderCreated
|
↓
InventoryPending
|
+---------+---------+
| |
Success Failure
| |
↓ ↓
PaymentPending CancelOrder
|
+----+----+
| |
Success Failure
| |
↓ ↓
Shipping ReleaseInventory
Pending |
| ↓
Success CancelOrder
|
↓
CompletedThis is a very good way to explain Saga in an interview.
38. Orchestrator Pseudocode
Conceptually:
public async Task Handle(OrderCreatedEvent message)
{
await ReserveInventory(message);
}When inventory succeeds:
public async Task Handle(InventoryReservedEvent message)
{
await ProcessPayment(message);
}Payment succeeds:
public async Task Handle(PaymentCompletedEvent message)
{
await CreateShipment(message);
}Shipment succeeds:
public async Task Handle(ShipmentCreatedEvent message)
{
await ConfirmOrder(message);
}Payment fails:
public async Task Handle(PaymentFailedEvent message)
{
await ReleaseInventory(message);
await CancelOrder(message);
}39. Compensation Table
A useful way to design a Saga is to create a compensation matrix.
| Forward Transaction | Compensation |
|---|---|
| Create Order | Cancel Order |
| Reserve Inventory | Release Inventory |
| Process Payment | Refund Payment |
| Create Shipment | Cancel Shipment |
| Apply Coupon | Restore Coupon |
| Allocate Loyalty Points | Return Loyalty Points |
For every business transaction, ask:
If this succeeds and something later fails, how do I undo its business effect?
If you cannot answer that, your Saga design isn't complete.
40. Data Consistency
A common question is:
How does Saga maintain data consistency?
It does not provide immediate strong consistency across all databases like a single ACID transaction.
Instead it provides:
Eventual Consistency
For example:
Initially:
Order = Pending
Inventory = Reserved
Payment = ProcessingAfter processing:
Order = Confirmed
Inventory = Reserved
Payment = CompletedOr if failure occurs:
Order = Cancelled
Inventory = Released
Payment = FailedThe system may temporarily have intermediate states, but eventually it reaches a valid business state.
41. Important: Don't Compensate Everything Blindly
Suppose:
Inventory reserved
Payment completed
Shipping failedYou shouldn't simply execute compensation commands without knowing the actual state.
Maintain Saga state:
InventoryReserved = true
PaymentCompleted = true
ShipmentCreated = falseThen compensation is based on completed steps.
if PaymentCompleted
RefundPayment()
if InventoryReserved
ReleaseInventory()
if OrderCreated
CancelOrder()42. Timeout Handling
Suppose Payment Service doesn't respond.
Payment Request
↓
Waiting...
↓
Waiting...
↓
TimeoutThe orchestrator should not wait forever.
For example:
Payment timeout = 5 minutesThen:
Payment Timeout
↓
Check payment status
↓
If unknown → retry/query provider
↓
If definitely failed → compensateThis is especially important with external payment gateways.
43. Why Payment Status Needs Special Care
Imagine:
Payment Service → Payment GatewayPayment request is sent.
Gateway processes it.
But response is lost.
Your service sees:
TimeoutYou must not automatically refund or retry blindly because the first payment might actually have succeeded.
You may accidentally charge the customer twice.
Instead use:
Idempotency KeyFor example:
OrderId = 1001
PaymentAttempt = 1
IdempotencyKey = ORDER-1001-PAYMENTThe payment provider should treat repeated requests with the same key as the same logical operation where its API supports idempotency.
44. Handling Concurrent Orders
Suppose only one laptop remains.
Stock = 1Two customers simultaneously order.
Customer A → Reserve
Customer B → ReserveInventory Service must use appropriate concurrency control.
For example:
UPDATE Inventory
SET AvailableQuantity = AvailableQuantity - 1
WHERE ProductId = @ProductId
AND AvailableQuantity >= 1;Then check:
Rows affected = 1Reservation succeeds.
If:
Rows affected = 0reservation fails.
This prevents overselling.
45. Saga Failure Scenarios
Scenario 1
Order ✓
Inventory ✓
Payment ✓
Shipping ✓Result:
CompletedScenario 2
Order ✓
Inventory ✗Compensation:
Cancel OrderScenario 3
Order ✓
Inventory ✓
Payment ✗Compensation:
Release Inventory
Cancel OrderScenario 4
Order ✓
Inventory ✓
Payment ✓
Shipping ✗Compensation:
Refund Payment
Release Inventory
Cancel OrderScenario 5
Compensation failsSolution:
Retry
↓
Retry
↓
Dead Letter Queue
↓
Operational Alert
↓
Manual Recovery46. Azure Implementation
Since you're working with Azure/.NET, one possible architecture is:
ASP.NET Core
|
↓
Order Service
|
↓
Azure Service Bus
|
↓
Saga Orchestrator
|
+----------------+
| |
↓ ↓
Inventory Payment
Service Service
| |
↓ ↓
Azure SQL Azure SQLAnd:
Shipping Service
|
↓
Azure SQLFor monitoring:
Application Insights
Azure MonitorFor secrets:
Azure Key Vault47. Azure Service Bus Structure
You might design:
Topic: ecommerce-eventsSubscriptions:
order
inventory
payment
shipping
sagaOr use separate command queues:
inventory-commands
payment-commands
shipping-commands
order-commandsFor orchestration, a command queue per service plus an event topic is often a clean conceptual model.
48. Message Flow
Example:
Order Service
|
| OrderCreated
↓
Service Bus
|
↓
Saga Orchestrator
|
| ReserveInventory
↓
Inventory Queue
|
↓
Inventory Service
|
| InventoryReserved
↓
Service Bus
|
↓
Saga
|
| ProcessPayment
↓
Payment QueueAnd so on.
49. Database Transactions Are Still Used
This is another important point.
Saga does not mean:
Don't use database transactions.
Each microservice should still use normal local transactions.
For example:
Inventory Service
BEGIN TRANSACTION
UPDATE Inventory
INSERT Reservation
INSERT OutboxMessage
COMMITThis is a local ACID transaction.
Saga coordinates these local transactions.
50. The Golden Rule
Think about Saga like this:
Saga
=
Multiple Local Transactions
+
Messages
+
State Machine
+
Compensating Transactions
+
Retry
+
Idempotency
+
Timeout Handling
+
ObservabilityThat is a much more accurate real-world definition than simply saying:
Saga is rollback for microservices.
It is not a distributed rollback mechanism.
51. Complete E-Commerce Flow
Here's the complete picture:
CUSTOMER
|
↓
API Gateway
|
↓
ORDER SERVICE
|
Create Order
|
↓
OrderCreated
|
↓
SAGA ORCHESTRATOR
|
Reserve Inventory
|
↓
INVENTORY SERVICE
|
Inventory Reserved
|
↓
SAGA ORCHESTRATOR
|
Process Payment
|
↓
PAYMENT SERVICE
|
Payment Success
|
↓
SAGA ORCHESTRATOR
|
Create Shipment
|
↓
SHIPPING SERVICE
|
Shipment Created
|
↓
SAGA ORCHESTRATOR
|
↓
Confirm OrderFailure:
Payment Failed
|
↓
Saga Orchestrator
|
+------→ Release Inventory
|
+------→ Cancel Order
|
↓
Saga Compensated52. Production-Grade Saga Checklist
When implementing Saga in a real application, consider all of these:
Saga ID
Correlation ID
Message ID
Idempotency
Saga state persistence
Local database transactions
Transactional Outbox
Reliable messaging
Retries
Exponential backoff
Timeouts
Dead-letter queues
Compensating transactions
Concurrency control
Optimistic/pessimistic locking where appropriate
Observability
Distributed tracing
Audit logging
Manual recovery
Poison message handling
53. Saga vs Two-Phase Commit
A common interview question is:
2PC
Coordinator
|
+--- DB1
+--- DB2
+--- DB3
Prepare
Prepare
Prepare
Commit
Commit
CommitIt attempts to provide distributed transactional atomicity, but can introduce blocking, coordination overhead, and operational complexity.
Saga:
Local Transaction
↓
Message
↓
Local Transaction
↓
MessageFailure:
Compensating TransactionSaga is generally better suited to independently deployable microservices where business operations can be compensated.
54. Interview Answer
If an interviewer asks:
"Explain Saga Design Pattern with an e-commerce example."
A strong answer would be:
"Saga is a distributed transaction pattern used in microservices where a business transaction is divided into a sequence of local transactions. Each service commits its own transaction independently. If a later transaction fails, the Saga executes compensating transactions for the previously completed operations.
For example, in an e-commerce system, placing an order may involve creating the order, reserving inventory, processing payment, and creating a shipment. If payment fails after inventory has been reserved, the Saga doesn't perform a database rollback across services. Instead, it executes a compensating transaction to release the inventory and then cancels the order.
Saga can be implemented using choreography, where services communicate through events, or orchestration, where a central Saga orchestrator manages the workflow. In a production system, I would also use an outbox pattern, idempotent message processing, retries, timeouts, dead-letter queues, correlation IDs, and persistent Saga state to achieve reliable eventual consistency."
55. Recommended .NET Architecture
For a production .NET implementation, I would structure it approximately like this:
src
│
├── OrderService
│ ├── Controllers
│ ├── Domain
│ ├── Application
│ ├── Infrastructure
│ └── Messaging
│
├── InventoryService
│ ├── Domain
│ ├── Application
│ ├── Infrastructure
│ └── Messaging
│
├── PaymentService
│ ├── Domain
│ ├── Application
│ ├── Infrastructure
│ └── Messaging
│
├── ShippingService
│ ├── Domain
│ ├── Application
│ ├── Infrastructure
│ └── Messaging
│
└── OrderSaga
├── StateMachine
├── Commands
├── Events
├── Consumers
└── PersistenceA particularly robust combination is:
Microservices
+
Saga Orchestration
+
Azure Service Bus
+
Transactional Outbox
+
Idempotent Consumers
+
Retry/Timeout
+
Dead Letter Queue
+
Azure SQL
+
Application InsightsThe key idea to remember is:
Saga does not make multiple databases behave like one database. It coordinates independent local transactions and uses compensating actions to bring the overall business process to a consistent state.
