Transactions &
Sagas
In a monolith, BEGIN TRANSACTION and COMMIT are magic. In microservices, they don't exist. How do you ensure money is deducted AND the order is shipped?
ACID in a Distributed World
The Problem
You split your monolith into an Order Service (Postgres) and a Payment Service (Stripe/Redis).
You cannot have strict ACID across network boundaries without significant performance penalties.
Two-Phase Commit (2PC)
The traditional "heavy" solution. A Coordinator asks everyone to prepare, then commit.
Coordinator asks A & B: "Can you commit?"
A & B lock resources and reply "Yes".
If ALL say "Yes", Coordinator says "Commit".
If ANY say "No", Coordinator says "Rollback".
blockWhy we hate it
- Blocking: Locks are held during the network round-trips. Slows everything down.
- Single Point of Failure: If Coordinator dies, everyone is stuck waiting.
- Not Cloud Native: Hard to implement with microservices/NoSQL.
The Saga Pattern
Long-Lived Transactions
Instead of one big lock, break the transaction into a sequence of local steps. If a step fails, execute Compensating Actions to undo previous steps.
Happy Path
Failure Path
Result: Eventual Consistency. The system is temporarily inconsistent, but eventually settles.
Orchestration vs Choreography
Choreography (Events)
Decentralized
Services listen to events and decide what to do. No central boss.
"OrderCreated" → Payment listens & charges → "PaymentProcessed" → Inventory listens...
- ✓ Loose coupling.
- ✗ Hard to track workflow (Mental complexity).
Orchestration (Command)
Centralized
A central Saga Orchestrator (State Machine) tells services what to do.
Orchestrator calls Order service. If OK, calls Payment service...
- ✓ Easy to debug & visualize state.
- ✗ Tighter coupling (Orchestrator must know flow).
Idempotency (Crucial)
Why you need it
In a distributed system, networks retry. You might receive the "Charge Payment" message twice.You must not charge the customer twice.
Idempotency Key
Pass a unique ID with every request (e.g., `order_id` or `uuid`).
return "Already Done";
} ELSE {
process();
save(key);
}