๐Ÿ’ฐ Transactions & Sagas in Distributed Systems

Welcome back to The Code Hut Distributed Systems series! In this post, we’ll discuss how to handle transactions that span multiple services or nodes — a common challenge in distributed systems. ๐Ÿ”„

⚠️ The Problem with Distributed Transactions

In a single database, transactions are straightforward: ACID guarantees ensure consistency. But in a distributed system:

  • ๐Ÿ› ️ Multiple services may need to update different databases
  • ๐ŸŒ Network failures can occur mid-transaction
  • ⏳ Traditional two-phase commit (2PC) can be slow and complex

๐Ÿ’ก The Saga Pattern

The Saga pattern manages distributed transactions as a series of local transactions:

  • Each service performs a local transaction
  • If a step fails, compensating transactions undo previous steps
  • Ensures eventual consistency without blocking resources

๐Ÿ—️ Saga Example

Below is a Saga involving five services, each with a normal action and a compensating action. This mirrors a real checkout flow but stays simple and framework-free.

๐Ÿ“ฆ Services Involved

  • OrderService – creates and cancels orders
  • PaymentService – charges and refunds customers
  • InventoryService – reserves and releases stock
  • ShippingService – books and cancels shipments
  • NotificationService – sends and retracts notifications

๐Ÿงฉ Service Implementations (Simple Java)


class OrderService {
    void create(Order order) {
        System.out.println("Order created");
    }
    void cancel(Order order) {
        System.out.println("Order cancelled");
    }
}

class PaymentService {
    void charge(Order order) {
        System.out.println("Customer charged");
    }
    void refund(Order order) {
        System.out.println("Payment refunded");
    }
}

class InventoryService {
    void reserve(Order order) {
        System.out.println("Stock reserved");
    }
    void release(Order order) {
        System.out.println("Stock released");
    }
}

class ShippingService {
    void scheduleShipment(Order order) {
        System.out.println("Shipment scheduled");
    }
    void cancelShipment(Order order) {
        System.out.println("Shipment cancelled");
    }
}

class NotificationService {
    void sendConfirmation(Order order) {
        System.out.println("Confirmation email sent");
    }
    void unsendConfirmation(Order order) {
        System.out.println("Confirmation email retracted");
    }
}

๐Ÿง  Full Saga Orchestrator

The Saga orchestrator runs the workflow and ensures compensations are applied in reverse order if anything fails.


class CheckoutSaga {

    private final OrderService orderService = new OrderService();
    private final PaymentService paymentService = new PaymentService();
    private final InventoryService inventoryService = new InventoryService();
    private final ShippingService shippingService = new ShippingService();
    private final NotificationService notificationService = new NotificationService();

    void run(Order order) {

        try {
            orderService.create(order);
            paymentService.charge(order);
            inventoryService.reserve(order);
            shippingService.scheduleShipment(order);
            notificationService.sendConfirmation(order);

            System.out.println("Checkout completed successfully!");

        } catch (Exception e) {
            System.out.println("Saga failed: " + e.getMessage());

            // Compensation chain (reverse order)
            safe(() -> notificationService.unsendConfirmation(order));
            safe(() -> shippingService.cancelShipment(order));
            safe(() -> inventoryService.release(order));
            safe(() -> paymentService.refund(order));
            safe(() -> orderService.cancel(order));

            System.out.println("Compensations executed. System consistent.");
        }
    }

    private void safe(Runnable action) {
        try { action.run(); } catch (Exception ignored) {}
    }
}

▶️ Running the Saga


public static void main(String[] args) {
    CheckoutSaga saga = new CheckoutSaga();
    Order order = new Order(); // placeholder
    saga.run(order);
}

๐Ÿงญ What This Example Shows

  • A realistic multi-step distributed workflow
  • Each service performs a local transaction
  • Compensations undo side effects when needed
  • No external dependencies or frameworks — just simple, clear Java

๐ŸŒ€ Orchestrated vs. Choreographed Sagas

๐ŸŽฎ Orchestration (central controller)

One service coordinates all steps and compensations.

Pros: easier to reason about, centralized logic

Cons: orchestration service may become complex

๐ŸŽผ Choreography (event-driven)

Each service publishes events and listens for others. E.g.:

  • OrderCreated → PaymentRequested → StockReserved → ShippingScheduled

Pros: highly decoupled

Cons: logic is distributed across many services

๐Ÿ When to Use the Saga Pattern

  • Microservices updating different databases
  • Systems needing eventual consistency
  • Long-running workflows (payments, reservations, travel booking)
  • Anti-pattern: tightly coupled ACID-like workflows (use a monolith instead)

✅ Benefits of Sagas

  • ❌ No need for distributed locks across services
  • ⚡ Scales well with microservices
  • ๐Ÿ”„ Supports eventual consistency
  • ๐Ÿ“‰ Reduces coupling between services

Next in the Series

In the next post, we’ll explore Fault Tolerance & Reliability, including patterns like replication, retries, and idempotency in distributed Java systems.

Label for this post: Distributed Systems

Comments

Popular posts from this blog

๐Ÿ› ️ The Code Hut - Index

๐Ÿ›ก️ Resilience Patterns in Distributed Systems

๐Ÿ›ก️ Thread-Safe Programming in Java: Locks, Atomic Variables & LongAdder