Friday, 19 December 2025

PLease describe in detailed with example in .net core if one of the microservice failed then how to rollback the tansaction in orchestrator saga pattern

 Below is a step-by-step, real-world explanation of Orchestrator Saga Pattern with rollback using .NET Core, exactly the way it is implemented in production systems and expected in senior interviews.


๐Ÿ”ท Problem Statement

You have multiple microservices, each with its own database.

Scenario:
Order → Payment → Inventory → Shipping

๐Ÿ‘‰ If any service fails, you must rollback previously completed steps.

❌ You cannot use DB transactions across services
✅ You must use Saga + Compensating Transactions


๐Ÿ”ท What Is Orchestrator Saga Pattern?

A central Saga Orchestrator controls:

  • The order of service calls

  • Success/failure handling

  • Compensation (rollback) logic

The orchestrator does NOT share DBs
It only invokes APIs / publishes commands


๐Ÿ”ท High-Level Flow Diagram

Client
  |
  v
Saga Orchestrator
  |
  |---> Order Service (Create Order)
  |---> Payment Service (Charge)
  |---> Inventory Service (Reserve)
  |---> Shipping Service (Create Shipment)

Failure Example

❌ Inventory fails → Orchestrator:

  • Refund Payment

  • Cancel Order


๐Ÿ”ท Step-by-Step Implementation in .NET Core


1️⃣ Order Service (Local Transaction)

Order Entity

public class Order
{
    public Guid Id { get; set; }
    public string Status { get; set; } // Pending, Confirmed, Cancelled
}

Order Controller

[HttpPost("create")]
public async Task<IActionResult> CreateOrder()
{
    var order = new Order { Id = Guid.NewGuid(), Status = "Pending" };
    _db.Orders.Add(order);
    await _db.SaveChangesAsync();
    return Ok(order.Id);
}

[HttpPost("cancel/{orderId}")]
public async Task<IActionResult> CancelOrder(Guid orderId)
{
    var order = await _db.Orders.FindAsync(orderId);
    order.Status = "Cancelled";
    await _db.SaveChangesAsync();
    return Ok();
}

2️⃣ Payment Service

[HttpPost("charge")]
public async Task<IActionResult> Charge(Guid orderId)
{
    // Simulate payment
    if (Random.Shared.Next(1, 10) < 3)
        return BadRequest("Payment Failed");

    _db.Payments.Add(new Payment { OrderId = orderId, Status = "Paid" });
    await _db.SaveChangesAsync();
    return Ok();
}

[HttpPost("refund")]
public async Task<IActionResult> Refund(Guid orderId)
{
    var payment = await _db.Payments.FirstAsync(p => p.OrderId == orderId);
    payment.Status = "Refunded";
    await _db.SaveChangesAsync();
    return Ok();
}

3️⃣ Inventory Service

[HttpPost("reserve")]
public async Task<IActionResult> Reserve(Guid orderId)
{
    // Simulate failure
    throw new Exception("Inventory not available");
}

4️⃣ Saga Orchestrator (Core Logic)

Orchestrator Controller

[HttpPost("place-order")]
public async Task<IActionResult> PlaceOrder()
{
    Guid orderId = Guid.Empty;

    try
    {
        // 1. Create Order
        orderId = await _orderClient.CreateOrder();

        // 2. Payment
        await _paymentClient.Charge(orderId);

        // 3. Inventory
        await _inventoryClient.Reserve(orderId);

        // 4. Shipping
        await _shippingClient.Create(orderId);

        return Ok("Order Completed");
    }
    catch (Exception ex)
    {
        await Compensate(orderId);
        return StatusCode(500, ex.Message);
    }
}

5️⃣ Compensation Logic (ROLLBACK)

private async Task Compensate(Guid orderId)
{
    if (orderId == Guid.Empty) return;

    try
    {
        await _paymentClient.Refund(orderId);
    }
    catch { /* log */ }

    try
    {
        await _orderClient.CancelOrder(orderId);
    }
    catch { /* log */ }
}

๐Ÿ‘‰ Each compensation is its own API call
๐Ÿ‘‰ Each service performs local DB rollback


๐Ÿ”ท Important Production Considerations ⭐


1️⃣ Idempotency (VERY IMPORTANT)

Compensation might be called multiple times.

if (payment.Status == "Refunded")
    return Ok();

2️⃣ Retry with Polly

Transient failures happen.

Policy
  .Handle<HttpRequestException>()
  .WaitAndRetryAsync(3, retry => TimeSpan.FromSeconds(2));

3️⃣ Timeout & Circuit Breaker

Avoid hanging orchestrator.

.TimeoutAsync(TimeSpan.FromSeconds(5))
.CircuitBreakerAsync(3, TimeSpan.FromMinutes(1));

4️⃣ Saga State Table (Recommended)

SagaState
{
    SagaId,
    OrderId,
    StepCompleted,
    Status
}

So recovery can happen after crash.


5️⃣ Event-Based Orchestrator (Better Scalability)

Instead of HTTP:

  • Azure Service Bus

  • Kafka

  • RabbitMQ


๐Ÿ”ท Why This Works

  • No distributed transaction

  • Each service owns its data

  • Rollback via compensating transactions

  • Eventual consistency


๐Ÿ”ท Interview-Ready Answer (Short)

“In orchestrator saga pattern, a central service controls the workflow. Each microservice performs a local transaction. If any service fails, the orchestrator triggers compensating transactions—like refunding payment or canceling the order—to logically rollback the system and achieve eventual consistency.”


๐Ÿ”ท When to Use Orchestrator Saga

ScenarioUse
Complex workflows
Payment flows
High observability needed
Simple async flows

If you want next, I can explain:

  • Event-driven saga using Kafka/Azure Service Bus

  • Saga persistence & recovery

  • Real production pitfalls

  • Comparison with choreography saga

Just tell me ๐Ÿ‘

No comments:

Post a Comment

Recent Post

How to Migrate in EF Core Without Data Loss