CodeOath
← All posts
Architecture & Patterns70 min total · 17 parts

Microservices vs. Monolith: Architecture Patterns and Trade-offs

Contents — Part 6 of 17: Data Ownership and the Saga Pattern
Part 6 of 17 · ~2 min

Data Ownership and the Saga Pattern

Each service owns its own data, and nothing else touches that database directly — other services only see it through the owning service's API. This is the single most important microservices rule, and it's also the one most commonly violated in systems that call themselves "microservices" but still share a database (see Common Mistakes, below) — sharing a database between services reintroduces tight coupling at the data layer even if the application code is split, and defeats most of what independent deployment was supposed to buy you: a schema change to a "shared" table now has to be coordinated across every service that touches it, exactly the coordination microservices were meant to eliminate.

The genuine cost of real per-service data ownership is the loss of cross-service ACID transactions. A monolith can do this in one transaction and rely on the database to guarantee it:

BEGIN;
UPDATE accounts SET balance = balance - 100 WHERE id = 'sender';
UPDATE accounts SET balance = balance + 100 WHERE id = 'receiver';
COMMIT;

Across two services with two separate databases, there's no single transaction that spans both. The Saga pattern replaces one atomic transaction with a sequence of local transactions, each in its own service, plus an explicit compensating action for every step, to undo it if a later step fails:

1. Orders service: create order (status: pending)         — local transaction, commits immediately
2. Payments service: charge the customer                  — local transaction
   -> if this fails: Orders service runs its compensating action (cancel the order)
3. Inventory service: reserve stock                        — local transaction
   -> if this fails: Payments service runs its compensating action (refund the charge)
                      Orders service runs its compensating action (cancel the order)
4. Orders service: mark order confirmed                    — local transaction

Notice what this actually buys you and what it doesn't: at no point is there a moment where "half the system" is atomically consistent the way a database transaction guarantees — there's a real window after step 2 and before step 3 where the customer has been charged but stock hasn't been reserved yet. The system reaches a consistent state eventually, once every step (forward or compensating) has run, not instantly. This is what eventual consistency actually means in practice: not "consistency is optional," but "consistency arrives after a sequence of steps completes, rather than atomically at a single instant." Compensating actions also aren't a true rollback — a compensating "cancel the order" doesn't undo the fact that a payment was briefly, genuinely charged; it issues a new, separate refund. Designing sagas well means designing every step to be safely compensatable, which is a real constraint on what a service's API needs to support (a cancelOrder endpoint, not just a createOrder one).