The Distributed Monolith: How Microservices Quietly Go Wrong
You split the monolith into ten services and somehow got slower, more fragile, and harder to deploy. That's a distributed monolith — the worst of both worlds. Here's how to spot and avoid it.
The promise of microservices is independent deployability: each team ships its service on its own schedule. The failure mode that eats that promise is the distributed monolith — services that are physically separate but so tightly coupled they must be built, tested, and deployed together. You paid the network tax and kept the coupling.
1. The symptoms
- Changing one service forces a coordinated release of three others.
- A single user action fans out into a chain of synchronous calls; if any hop is down, the whole thing fails.
- Services share a database, so a schema change ripples everywhere.
- You can't test a service without spinning up half the system.
2. Shared database is the original sin
If two services read and write the same tables, they are one service wearing two hats. The schema becomes a public API nobody owns, and every migration is a cross-team negotiation. Each service must own its data and expose it only through its API or events — no reaching into another service's tables.
3. Synchronous chains multiply failure
A request that hops A→B→C→D synchronously has availability 0.99⁴ ≈ 96% even if every service is 99% up, and its latency is the sum of the chain. Break the chain: make non-critical work asynchronous via events, and collapse chatty call sequences so one service doesn't orchestrate five others on the hot path.
A → B → C → D // 4 hops: latency adds up, failure compounds
A emits event → B, C, D react independently // decoupled, resilient
4. Boundaries follow the domain, not the org chart
Draw service boundaries around business capabilities that change together (a bounded context), not around technical layers or whoever happened to write the code. A boundary that needs constant cross-service transactions is in the wrong place — that data wants to live together.
5. Version contracts; don't break them in lockstep
Independent deploy requires backward-compatible contracts. Add fields, don't repurpose them; support old and new for a deprecation window. The day a producer change requires every consumer to redeploy simultaneously is the day you've rebuilt the monolith over HTTP.
Rules of thumb
- One service owns its data. No shared tables, ever.
- Default to async events; reserve synchronous calls for genuinely interactive paths.
- Boundaries around bounded contexts, not layers or teams.
- If a change forces a lockstep multi-service deploy, your boundaries or your contracts are wrong.
- Don't start with microservices. Extract them from a monolith once the seams are obvious.