A monolithic application is easy to reason about: if the database goes down, the app goes down. In a microservices architecture, you might have 50 different services running on 500 different servers communicating over an unreliable network. What happens if the Payment Service crashes right after the Inventory Service deducts an item? You must architect for failure.


Module 1: The CAP Theorem

The CAP theorem states that a distributed data store can only provide two of the following three guarantees simultaneously:

CAP Properties

  • Consistency (C): Every read receives the most recent write or an error.
  • Availability (A): Every request receives a non-error response, without the guarantee that it contains the most recent write.
  • Partition Tolerance (P): The system continues to operate despite an arbitrary number of messages being dropped by the network.

Because networks will ALWAYS fail (Partition Tolerance is mandatory), architects must choose between CP (Consistency) or AP (Availability) during an outage. A bank ledger chooses CP (rejects transactions if sync fails). A social media feed chooses AP (shows you slightly outdated posts rather than crashing).


Module 2: The Circuit Breaker Pattern

If Service A calls Service B, and Service B is experiencing a severe slowdown, Service A's requests will start piling up in memory waiting for a timeout, eventually causing Service A to crash as well (Cascading Failure). A Circuit Breaker prevents this.

circuit-breaker.js (Concept)javascript
class CircuitBreaker {
  constructor(requestFunction) {
    this.requestFunction = requestFunction;
    this.state = 'CLOSED'; // NORMAL: Requests flow through
    this.failureCount = 0;
    this.failureThreshold = 5;
    this.cooldownTimer = null;
  }

  async fire() {
    if (this.state === 'OPEN') {
      // Fast fail! Do not even attempt the network request
      throw new Error("Circuit is OPEN. Service B is offline.");
    }

    try {
      const response = await this.requestFunction();
      this.reset(); // Success!
      return response;
    } catch (err) {
      this.failureCount++;
      if (this.failureCount >= this.failureThreshold) {
        this.trip(); // Open the circuit after 5 consecutive failures
      }
      throw err;
    }
  }

  trip() {
    this.state = 'OPEN';
    // Check if the service recovered after 30 seconds (HALF-OPEN state)
    setTimeout(() => { this.state = 'HALF-OPEN'; }, 30000);
  }
}

Module 3: Distributed Transactions (The Saga Pattern)

In a monolith, you use an ACID SQL transaction to deduct inventory and charge a credit card. If either fails, you ROLLBACK the database. In microservices, the Inventory DB and Payment DB are separate. You cannot use standard transactions. We use the Saga Pattern.

A Saga is a sequence of local transactions. Each local transaction updates the database and publishes an event to trigger the next transaction.

Saga Execution Flow

  • Step 1: Order Service creates an order (Status: PENDING) and emits OrderCreated.
  • Step 2: Inventory Service listens, reserves stock, and emits InventoryReserved.
  • Step 3: Payment Service listens, attempts to charge card... CARD DECLINED! Emits PaymentFailed.

Module 4: Idempotency

If a client sends a payment request, but their internet cuts out before receiving the 200 OK response, they will likely click 'Pay' again. A distributed system MUST be idempotent: performing the same operation multiple times must yield the exact same result as performing it once.

idempotency.jsjavascript
app.post('/api/charge', async (req, res) => {
  // The client generates a unique UUID (Idempotency-Key) and attaches it to the header
  const idempotencyKey = req.headers['idempotency-key'];
  
  // Check if we already processed this exact request
  const existingTx = await db.transactions.findByKey(idempotencyKey);
  if (existingTx) {
    return res.status(200).json({ status: "SUCCESS", msg: "Already processed" });
  }

  // Process payment normally...
  await Stripe.charge(req.body.amount);
  
  // Save the idempotency key so future retries are ignored
  await db.transactions.create({ key: idempotencyKey, amount: req.body.amount });
  
  res.status(200).json({ status: "SUCCESS" });
});