Circuit Breaker Pattern: Building Resilient Microservices
Deep dive into the circuit breaker pattern for fault-tolerant microservices. State transitions, fallback strategies, retries, bulkheads, and timeouts.

Two-phase commit sounds elegant in textbooks. In production microservices, it's a coordination nightmare. Services go down, networks partition, and holding locks across distributed systems kills throughput. The Saga pattern offers a pragmatic alternative: break a distributed transaction into a sequence of local transactions, each with a compensating action if something goes wrong.

In a monolith, a single database transaction ensures atomicity. Transfer money between accounts? One transaction, one commit, one rollback if anything fails. In microservices, the data lives in different databases owned by different services. There is no shared transaction coordinator.
Two-phase commit (2PC) tries to solve this by introducing a coordinator that asks all participants to prepare, then commit. But 2PC has serious drawbacks:
I've seen teams attempt 2PC across microservices with XA transactions in Java. It works in development. In production, the first time a network partition hits during the prepare phase, you end up with one service committed and another stuck holding locks, and a team scrambling to manually reconcile state at 3 AM.
A saga is a sequence of local transactions. Each step commits independently. If step N fails, the saga executes compensating transactions for steps N-1 through 1, undoing the effects of the previous steps.
T1 → T2 → T3 → T4 (success)
T1 → T2 → T3 ✗ → C2 → C1 (compensation)
Each Ti is a local transaction. Each Ci is its compensating transaction.
If payment fails at step 3:
There are two ways to coordinate saga steps:
Each service publishes events and listens for events from other services. No central coordinator: the saga emerges from the event flow.
The debugging problem with choreography is real. When an order gets stuck in PENDING, you have to trace events across multiple services and message brokers to figure out where the chain broke. There's no single place that shows you the saga's current state. For a two-step saga this is manageable; for anything beyond three services, it becomes a serious operational burden.
A central orchestrator service directs the saga. It tells each participant what to do and handles compensation on failure.
The orchestrator maintains a saga execution log: a persistent record of which steps have completed, which are pending, and which need compensation. This log is what makes orchestration debuggable.
public class SagaExecution {
private String sagaId;
private SagaStatus status; // RUNNING, COMPENSATING, COMPLETED, FAILED
private List<StepExecution> steps;
private Instant startedAt;
private Instant completedAt;
}
public class StepExecution {
private String stepName;
private StepStatus status; // PENDING, SUCCEEDED, FAILED, COMPENSATED
private String request;
private String response;
private Instant executedAt;
}When something goes wrong, you query the saga execution log for the saga ID and see exactly which step failed, what the response was, and whether compensation has started. This level of visibility is invaluable in production.
Compensation is the hardest part of sagas. Not every action has a clean inverse:
A pattern that works well in practice is to design saga steps as reservations rather than final actions. Reserve inventory (don't decrement it), authorize payment (don't capture it), schedule shipping (don't dispatch it). The final step confirms all reservations. If anything fails, releasing a reservation is much simpler than reversing a completed action.
-- Step: Reserve inventory (not a final decrement)
UPDATE inventory
SET reserved = reserved + 1
WHERE sku_id = :sku AND (available - reserved) > 0;
-- Compensation: Release reservation
UPDATE inventory
SET reserved = reserved - 1
WHERE sku_id = :sku AND reserved > 0;
-- Final confirmation: Convert reservation to actual decrement
UPDATE inventory
SET reserved = reserved - 1, available = available - 1
WHERE sku_id = :sku;Sagas introduce eventual consistency, which means you need strong observability to maintain confidence in your system:
Deep dive into the circuit breaker pattern for fault-tolerant microservices. State transitions, fallback strategies, retries, bulkheads, and timeouts.

Scaling contract testing across dozens of microservices. Broker workflows, CI integration, contract versioning, and sustainable testing practices.

How Pact contract testing enables consumer-driven contracts for microservices. Real-world examples, failure modes, and CI/CD best practices.
