When Monoliths Break: Rethinking Transactions in Microservices
One “Buy Now” spans payments, orders and inventory. Why ACID stops at the service boundary, and what replaces it: 2PC and sagas.
Read the original on Medium ↗At first, life in a monolithic app is simple. One codebase, one database, and a single transaction can handle everything: deduct balance, update inventory, create order. If something fails, the database rolls back — clean and safe.
But as the system grows, you split into microservices: payments, orders, inventory, notifications. Suddenly, that same “Buy Now” action spans multiple databases. Payment might succeed while order creation fails, leaving data inconsistent and customers frustrated.
§1Important Note
Before jumping into microservices, it’s crucial to understand that they’re not a silver bullet and shouldn’t be chosen just to “flex” or follow hype. Microservices are resource-intensive, introduce operational complexity, and are designed for systems that truly need them.
If your application doesn’t handle a high volume of requests per day, and you don’t have a team of developers to maintain independent services, then a monolithic architecture is often the more practical and cost-effective choice.
The recommended journey usually looks like this:
- Start with a monolith — it’s simpler, faster to build, and easier to maintain when your team is small.
- Scale horizontally (load balancing, caching, database optimization, etc.) — many performance issues can be solved without splitting the system.
- Adopt microservices only when necessary — for example, when your system grows large enough that teams need to work independently on separate components, or when scaling different parts of the system independently becomes essential.
Otherwise, prematurely adopting microservices is likely to add more problems than it solves.
§2Monolithic Server
§31. Transactions in a Monolithic Server
In a monolithic architecture, the application logic and data models typically live in a single repository, backed by one central database. This makes it straightforward to manage transactions because everything is contained within a single system. Developers can rely directly on the database’s built-in transaction management features.
§4traditional ACID transactions
Most relational databases (PostgreSQL, MySQL, Oracle, SQL Server, etc.) implement ACID transactions to guarantee data integrity:
- Atomicity — all or nothing. A transaction succeeds entirely or fails entirely.
- Example: if you transfer money from one account to another, both the debit and credit must succeed; otherwise, both roll back.
- Consistency — ensures that the database transitions from one valid state to another.
- Example: enforcing constraints (e.g., balance cannot go negative) keeps data valid before and after the transaction.
- Isolation — ensures that concurrent transactions don’t interfere with each other.
- Example: if two users withdraw money at the same time, isolation prevents “double spending.”
- Durability — once a transaction is committed, it persists even if the system crashes.
- Example: after a successful money transfer, the change is permanently stored on disk.
§5Micro-services Server
In microservices, each service usually manages its own database.
This makes distributed transactions difficult because you can’t rely on a single database transaction to ensure consistency across all services. Instead, you need coordination mechanisms when multiple services must update their data together (e.g., payment + order + inventory).
§61. Two-Phase Commit (2PC) Pattern
2PC is a protocol for distributed transactions where a central coordinator ensures that either all services commit or all rollback, preserving consistency.
§7Phase 1: Prepare (Voting Phase)
- The coordinator sends a PREPARE request to all participating services (or databases).
- Example in SQL (PostgreSQL):
PREPARE TRANSACTION 'tx123';- This means the local DB executes the transaction but does not commit yet. Instead, it locks the resources and marks them as “prepared.”
- Each participant replies:
- YES (Prepared) → if it can safely commit.
- NO (Abort) → if it encountered an error or cannot guarantee success.
At this point, resources are locked and waiting for the final decision.
Note: Most ORMs do not natively support prepared transactions. To use two-phase commit (2PC), you need to issue the PREPARE TRANSACTION, COMMIT PREPARED, and ROLLBACK PREPARED statements manually via raw SQL.
§8Phase 2: Commit or Rollback (Decision Phase)
- If all participants replied YES:
- The coordinator sends COMMIT TRANSACTION 'tx123'; to each service.
- All databases/services finalize the transaction.
- If any participant replied NO:
- The coordinator sends ROLLBACK TRANSACTION 'tx123'; to each service.
- All databases/services undo their local transaction.
§92. Saga Pattern
The Saga pattern works by breaking a global transaction into a series of local transactions that are coordinated asynchronously.
- Each service does its local transaction (which commits to its own DB).
- If one fails later, we run a compensating transaction (undo logic) for the already successful ones.
It’s like a chain of transactions with undo steps.
Compensating Transaction Pattern
The Compensating Transaction Pattern is a strategy used to maintain consistency when a business operation fails. Instead of relying on a single atomic transaction (like in a monolithic system), compensating transactions are executed to undo or reverse the steps of a previously completed operation.
- In monolithic architectures, a single database transaction usually ensures atomicity (all-or-nothing). But in some long-running processes or external operations, compensating transactions may still be needed.
- In microservices architectures, each service has its own database, which makes distributed transactions harder. That’s why compensating transactions (often used in the Saga Pattern) are more common in microservices. When one step in a workflow fails, the system triggers compensating actions in the already completed steps to roll back the overall process.
In short, compensating transactions are a way to “manually undo” work when automatic rollback isn’t possible across services.
for the saga pattern has two approche :
§101. Choreography (Event-based)
- No single cordinator → The system is decentralized.
- Each service listens for specific events.
- When it finishes its local transaction, it emits another event.
- Other services subscribed to that event react and do their part.

§112. Orchestration (Coordinator-driven)
- Centralized: A single orchestrator service controls the saga.
- The orchestrator tells each service what to do next.
- If a step fails, the orchestrator triggers compensating transactions.
- Services don’t talk to each other directly — they only respond to the orchestrator.

§12Conclusion
In summary, there are several transaction patterns in microservices beyond the ones we discussed, such as the Outbox Pattern, Transactional Messaging, and others designed to handle reliability and consistency in distributed systems.
However, these patterns also highlight a key reality: microservices add significant complexity. Without a strong team of developers and solid infrastructure, it’s easy to end up with more problems than solutions. For many projects, starting with a monolithic architecture remains the smarter choice until scaling demands truly justify the move.
§13Next Blog Preview
In the next article, we’ll explore how to use Debezium to trigger updates from the database. This will show how change data capture (CDC) can help keep systems synchronized in real time.