The Distributed Monolith: Why the Microservices Hype is Killing Early-Stage Velocity
The Distributed Monolith: Why the Microservices Hype is Killing Early-Stage Velocity
Introduction
In the mid-2010s, the engineering world fell in love with a dream: independent services, polyglot persistence, and teams that could ship code without ever talking to each other.
For companies like Netflix and Uber, microservices were a necessity. For the 10-person startup trying to find product-market fit, they were a disaster.
I have spent years helping companies "un-microservice" their stacks. Most of them didn't actually build microservices; they built a Distributed Monolith. They took all the coupling of a single application and added the latency, complexity, and network instability of a distributed system.
Section 1: The Anatomy of a Distributed Monolith
A true microservice architecture is defined by independence. If you can't deploy Service A without also deploying Service B at the same time, you don't have microservices.
The Warning Signs:
- Synchronous Chains: Service A calls Service B calls Service C. If C goes down, the whole chain fails. You’ve just multiplied your failure surface area.
- Shared Databases: Multiple services reading and writing to the same database schema. You have zero data encapsulation.
- Complex Orchestration: You need a 50-step Kubernetes deployment script just to update a UI feature.
In a startup, your greatest asset is speed. A distributed monolith is the quickest way to kill it.
Section 2: The Overhead of Distributed Complexity
When you break a monolith into services, you aren't removing complexity—you're moving it into the network.
- Network Latency: Instead of an in-memory function call (nanoseconds), you have a serialized HTTP request over the wire (milliseconds).
- Observability Burden: You now need distributed tracing (Jaeger/Zipkin) just to figure out why a request is slow.
- Partial Failures: What happens if Service A succeeds but the callback to Service B fails? You now need to implement Sagas or distributed transactions to maintain consistency.
Are these problems solvable? Yes. Is it worth solving them when you only have 1,000 customers? Almost never.
Section 3: Practical Application: The Modular Monolith
If you want the benefits of clean boundaries without the pain of the network, build a Modular Monolith.
Organize your code by domain (e.g., Users, Billing, Inventory) inside a single repository and a single process.
- Strict API boundaries: Use internal interfaces. Don't let the Billing module reach directly into the User's database table.
- In-memory events: Use a simple local event bus for inter-module communication.
- The "Easy" Migration: If a specific module truly needs separate scaling or a different team, spinning it out into a microservice is a weekend task because the boundaries are already clean.
Section 4: Common Mistakes: Scaling for the "Wrong" Reasons
The biggest mistake is scaling for team size incorrectly. "We have 20 engineers, so we need microservices to avoid git merge conflicts."
This is an organizational failure masked as a technical one. You can solve merge conflicts with better code reviews, smaller pull requests, and clear ownership of directories. You don't need a service mesh to fix a culture problem.
Another mistake is believing that microservices are "cleaner." In reality, they are often used as an excuse to avoid hard discussions about domain modeling. Bad code in a monolith is bad code; bad code in microservices is bad code that’s also hard to debug.
Final Thought
Start with a monolith. Structure it well. Guard your boundaries. Only break it apart when the pain of the monolith (deployment time, scaling limits) outweighs the massive pain of distributed systems. Velocity is your primary advantage—don't trade it for a trendy architecture.