Many microservice architectures are just distributed monoliths: a pervasive anti-pattern
Defining the distributed monolith
A distributed monolith occurs when a system split into multiple services keeps the interconnected nature of a traditional monolith. Services look separate on paper but behave as one unit. The dependencies run too deep, so deployments have to be coordinated and scalability is undermined.
- Many microservice architectures have tight coupling and shared dependencies, so they function as distributed monoliths rather than independently scalable services.
- Teams often rush into microservices without clear domain boundaries. The result is systems where services must be deployed together and changes ripple across the whole architecture.
- Industry reports and case studies suggest the anti-pattern is widespread: it pairs the rigidity of a monolith with the operational overhead of distributed systems.
Key symptoms to watch for
Look for signs such as synchronized deployments across services, shared databases, and chatty synchronous calls that create latency chains. If a minor update requires touching half the services, the architecture has likely devolved into this state.
Root causes and consequences
This pattern comes from splitting by technical layer instead of by domain boundary. The outcome is worse performance and higher complexity than either a pure monolith or well-designed microservices.
Path to true microservices
Adopt database-per-service, asynchronous messaging, and bounded contexts from Domain-Driven Design. That is how you get loose coupling and independent evolution.
Introduction: the allure and the pitfall
Microservices promised better scalability, faster deployments, and team autonomy. Teams flocked to the architecture and pictured loosely coupled services that could evolve on their own. Too many implementations fell short. Instead of agile, resilient systems, they built distributed monoliths: services that are physically separated but stay logically fused. The monolith’s brittleness stuck around, and distributed-system problems such as network latency and failure cascades piled on top.
Understanding the architectures: a comparative framework
To understand why distributed monoliths keep appearing, consider the spectrum of application designs. A traditional monolith puts all logic in one deployable unit, which is simple but scales only so far. Microservices split it into autonomous services aligned with business domains. Distributed monoliths sit in the middle: fragmented in form, monolithic in function.
| Aspect | Monolith | True Microservices | Distributed Monolith |
|---|---|---|---|
| Deployment | Single unit, simple coordination | Independent per service | Lock-step across services |
| Coupling | Tight within codebase | Loose via APIs/events | Tight via sync calls/shared DB |
| Scalability | Vertical, uniform | Horizontal, per service | Pseudo-horizontal, bottleneck-prone |
| Operational Overhead | Low | High (monitoring, orchestration) | Highest (monolith rigidity + distro complexity) |
| Change Propagation | Localized within repo | Contained to one service | Ripples across multiple services |
| Failure Isolation | System-wide | Service-level | Cascading due to dependencies |
The table shows why distributed monoliths underperform: they demand microservices tooling without delivering independence.
The hallmarks: six telltale symptoms
Lock-step deployment. Services deploy together because one change breaks others. Refactor shared code into libraries or split the infrastructure.
Version coupling. Hard-coded version dependencies force config updates. DNS routing lets versions evolve cleanly, like CNAMEs pointing to stable endpoints.
Bidirectional dependencies. Circular calls complicate testing. Break the cycles with events or sagas.
Shared database access. Services bypass APIs to query foreign schemas, which couples them at the data layer. Enforce separate schemas or databases with row-level security.
Shared queues. Direct queue manipulation risks breakage. Give ownership of each queue to one service.
Enterprise Service Bus (ESB) overreliance. Central buses obscure consumers and hide couplings. Replace them with point-to-point or pub-sub patterns.
Why does this happen?
The rush starts with greenfield microservices fervor. Teams split by technical layer (UI, business logic, data) instead of by business capability. Justin Etheredge warns about this: “You’ve traded the simplicity of a single codebase for a deeply intertwined distributed system.” Legacy migrations make things worse when they carve up a monolith without Domain-Driven Design (DDD).
Common triggers:
- Inadequate boundaries, which lead to synchronous overload.
- Shared persistence, which undermines isolation.
- Neglected API contracts, which block independent evolution.
Sam Newman’s Monolith to Microservices argues for evolutionary decomposition over big-bang rewrites. It recommends patterns like Strangler Fig to extract services incrementally while keeping each one deployable.
The toll: performance, cost, and team friction
Distributed monoliths perform worse than monoliths: network hops replace in-process calls, but changes still need coordination across teams. A 20ms latency spike can slash throughput by ~80% in chatty setups. Operationally, they demand Kubernetes orchestration and still end up with monolith-like rigidity.
Teams suffer: developers juggle multiple repos, deployments halt on inter-service bugs, and scaling mimics uniform monolith growth.
Escaping the trap: strategies for redemption
Prevention beats cure. Start with a modular monolith and organize code into deployable modules so the boundaries surface on their own.
Core remedies:
- Bounded contexts via DDD: align services to subdomains (e.g., Order Management separate from Inventory).
- Database per service: polyglot persistence avoids schema locks.
- Async-first communication: events over RPCs for resilience.
- Chaos engineering: inject failures to expose couplings.
- Continuous refactoring: monitor deployment frequency and coupling metrics.
| Remediation Technique | Benefit | Example Tool/Pattern |
|---|---|---|
| DDD Bounded Contexts | Clear ownership | EventStorming workshops |
| Async Messaging | Decouples timing | Kafka, RabbitMQ |
| API Gateways | Abstracts changes | Kong, AWS API Gateway |
| Saga Pattern | Distributed transactions | Choreography/Orchestration |
For existing systems, Newman’s patterns, such as Branch by Abstraction and Parallel Run, make safe extraction possible.
Conclusion: choose wisely, evolve thoughtfully
Martin Fowler advises: “Don’t even consider microservices unless you have a system that’s too complex to manage as a monolith.” Most teams should start with a sensible monolith. When scaling demands distribution, put independence first. The distributed monolith catches the unprepared. DDD, async patterns, and steady refactoring are how you keep the promise of microservices.
Sources
You’re Not Actually Building Microservices
Microservices anti-pattern: The Distributed Monolith
Is Your Microservice a Distributed Monolith?
6 Symptoms of a Distributed Monolith
Monolith to Microservices by Sam Newman