Many Microservice Architectures Are Just Distributed Monoliths: Pervasive Anti-pattern
Defining the Distributed Monolith
A distributed monolith occurs when a system divided into multiple services retains the interconnected nature of a traditional monolith. Services appear separate on paper but behave as a single unit due to excessive dependencies, forcing coordinated deployments and undermining scalability.
- Many microservice architectures suffer from tight coupling and shared dependencies, effectively functioning as distributed monoliths rather than true independently scalable services.
- Teams often rush into microservices without clear domain boundaries, leading to systems where services must be deployed together and changes ripple across the entire architecture.
- Evidence from industry experts and case studies indicates this anti-pattern is widespread, combining the rigidity of monoliths with the operational overhead of distribution.
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 emerges from layer-based splitting rather than domain-driven boundaries, resulting in poorer performance and higher complexity than either pure monoliths or well-designed microservices.
Path to True Microservices
Adopt database-per-service, asynchronous messaging, and bounded contexts from Domain-Driven Design to ensure loose coupling and independent evolution.
Introduction: The Allure and the Pitfall
Microservices promised revolutionary scalability, faster deployments, and team autonomy. Organizations flocked to this architecture, envisioning loosely coupled services that could evolve independently. Yet, numerous implementations have fallen short. Instead of agile, resilient systems, teams built distributed monoliths, physically separated services that remain logically fused, inheriting the monolith’s brittleness while amplifying distributed system woes like network latency and failure cascades.
Understanding the Architectures: A Comparative Framework
To grasp why distributed monoliths proliferate, consider the spectrum of application designs. Traditional monoliths consolidate all logic into one deployable unit, offering simplicity but scalability limits. Microservices fragment this into autonomous services aligned with business domains. Distributed monoliths occupy a dangerous middle ground: 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 |
This table highlights 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 infrastructure.
Version Coupling: Hard-coded version dependencies force config updates. Use DNS routing for seamless evolution, like CNAMEs pointing to stable endpoints.
Bi-Directional Dependencies: Circular calls complicate testing. Break cycles with events or sagas.
Shared Database Access: Services bypass APIs to query foreign schemas, coupling at the data layer. Enforce separate schemas or databases with row-level security.
Shared Queues: Direct queue manipulation risks breakage. Assign ownership to one service.
Enterprise Service Bus (ESB) Overreliance: Central buses obscure consumers, fostering hidden couplings. Replace with point-to-point or pub-sub patterns.
Why Does This Happen?
The rush begins with greenfield microservices fervor. Teams split by technical layers (UI, business logic, data) rather than business capabilities, as Justin Etheredge warns: “You’ve traded the simplicity of a single codebase for a deeply intertwined distributed system.” Legacy migrations exacerbate this, carving monoliths arbitrarily without Domain-Driven Design (DDD).
Common triggers:
- Inadequate boundaries leading to synchronous overload.
- Shared persistence undermining isolation.
- Neglecting API contracts for independent evolution.
Sam Newman’s Monolith to Microservices emphasizes evolutionary decomposition over big-bang rewrites, advocating patterns like Strangler Fig to extract services incrementally while preserving deployability.
The Toll: Performance, Cost, and Team Friction
Distributed monoliths perform worse than monoliths: network hops replace in-process calls, yet changes still coordinate across teams. A 20ms latency spike can slash throughput by ~80% in chatty setups. Operationally, they demand Kubernetes orchestration for 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 trumps cure. Start with a modular monolith, organize code into deployable modules—to discover boundaries organically.
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—Branch by Abstraction, Parallel Run-enable safe extraction.
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 embrace sensible monoliths first. When scaling demands distribution, prioritize independence. The distributed monolith lurks for the unprepared-arm yourself with DDD, async patterns, and relentless refactoring to build architectures that deliver on microservices’ promise.
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