Course contentsShow
← Node.js
Lesson 873 of 1,9808. Data Persistence and ORM IntegrationFree lesson

Implementing Distributed Transactions

You will coordinate data consistency across multiple independent services.

In this lesson, you will learn how to maintain data consistency when a single business operation spans across multiple, independent database services.

The Challenge of Distributed Systems

In a monolithic application, you can use a standard transaction to ensure that either all database operations succeed or all are rolled back. However, when your architecture uses polyglot persistence (e.g., an Order Service in MongoDB and an Inventory Service in PostgreSQL), a local database transaction cannot span across those service boundaries. If the Order Service succeeds but the Inventory Service fails, your system enters an inconsistent state.

The Saga Pattern

Because true distributed ACID transactions are rarely performant in NoSQL environments, we use the Saga Pattern. A Saga is a sequence of local transactions. Each local transaction updates a database and publishes an event or message to trigger the next transaction in the sequence.

If a local transaction fails, the Saga executes a series of compensating transactions to undo the changes made by the preceding local transactions.

Imagine a simple "Order Processing" flow:

  1. Service A (Orders): Creates a "Pending" order record.
  2. Service B (Inventory): Attempts to reserve stock.
  3. Failure: If Service B fails, it sends an "InventoryFailed" event back to Service A.
  4. Compensation: Service A receives the event and updates the order status to "Cancelled."

Implementation Concept

In Node.js, you typically coordinate this using a message broker. Your code might look like this:

async function placeOrder(orderData) {
  try {
    const order = await OrderService.create(orderData); // Local Tx 1
    await MessageQueue.publish('ORDER_CREATED', order); // Trigger next step
  } catch (error) {
    await handleFailure(orderData); // Rollback if initial step fails
  }
}

// Listener for failure
MessageQueue.subscribe('INVENTORY_FAILED', async (msg) => {
  await OrderService.updateStatus(msg.orderId, 'CANCELLED'); // Compensating Tx
});

By decoupling these services through events, you ensure that even if one service is temporarily unavailable, the system can eventually reach a consistent state (Eventual Consistency). You are essentially trading immediate atomic consistency for high availability and scalability.

Key Takeaway: Since distributed systems cannot rely on atomic locking across services, use the Saga pattern with compensating transactions to ensure your data eventually reaches a consistent state.