The False Binary
Monolith and microservices aren't the only options - and picking microservices too early buys distributed-systems pain for problems you don't have. The distributed monolith, the worst of both.
On this page
"Should we build a monolith or microservices?" is one of the most consequential - and most botched - architectural questions. It's usually posed as a binary between a big ball of mud and a fleet of tiny services, and teams reach for microservices to feel modern, inheriting a mountain of distributed-systems complexity to solve problems they don't have. The truth: there's a powerful middle option, and the right default for most teams is not microservices.
The false binary
The question assumes two choices. There are at least three:
- The "big ball of mud" monolith - one deployable, no internal structure, everything tangled. This is what
people mean by "monolith" when they disparage it (and what
ledger-legacyis). - Microservices - many independently deployable services, each its own process and database, communicating over the network.
- The modular monolith - one deployable, but internally split into well-bounded modules with enforced boundaries. The structure of microservices, the simplicity of a single process.
The disparaged "monolith" is really the unstructured monolith. A modular monolith gets you clean boundaries without the distributed tax - and it's the subject of this whole module.
What microservices actually cost
Microservices solve real problems - independent scaling, independent deployment, team autonomy at large scale - but they're not free. Splitting a process into networked services buys you:
- Distributed systems, everywhere. Every in-process method call becomes a network call that can be slow, fail, or time out. You now need retries, circuit breakers, and idempotency (recall the Spring Cloud module).
- No more ACID transactions across services. A change spanning two services can't be one transaction; you're into sagas and eventual consistency for things that were trivially atomic before.
- Operational overhead. Many deployables to build, deploy, monitor, and trace; service discovery, an API gateway, distributed tracing - all now mandatory, not optional.
- Hard-to-move boundaries. Get a service boundary wrong and fixing it means coordinating changes across network-separated, independently deployed codebases - far harder than moving a method between packages.
That last point is decisive: you rarely get boundaries right the first time, and in a monolith they're cheap to move. In microservices they're set in concrete.
The distributed monolith: the worst of both
The most common failure is landing in neither good option but the trap between them: a distributed monolith - services that are split across the network but still tightly coupled, so you can't deploy one without the others, and a single request fans out through five of them. You've paid microservices' full operational cost and kept the monolith's coupling, gaining nothing. It usually happens when a team splits into services before understanding the domain's boundaries - splitting by technical layer or premature guesswork instead of real seams.
Start with a modular monolith - almost always
The pragmatic default, championed by many senior engineers, is: begin with a modular monolith, get the module boundaries right in a codebase where they're cheap to move, and extract a module into a service only when a concrete pressure demands it (a module needs independent scaling, a separate team must own its release, or a distinct tech stack is required). Microservices are a solution to organizational and scaling problems, not a starting architecture. Don't take on distributed systems until the problem is distributed.
A big-ball-of-mud monolith is a single room where every team works at one giant shared desk, papers intermingled
- chaos. Microservices are a sprawling campus of separate buildings across a city: each team gets autonomy, but now every conversation is a scheduled video call that can drop, you can't just lean over to ask a question, and moving a team to a different building is a construction project. The modular monolith is one building with clearly separated, walled offices: teams have their own space and boundaries, yet collaboration is a walk down the hall (an in-process call), and you can rearrange the walls over a weekend. You only build the separate campus when a team genuinely needs to be in another city - not by default.
A 6-person startup is building a new product. Their CTO, excited by a conference talk, proposes launching with 12 microservices - separate services for users, billing, notifications, catalog, etc. - each with its own database and deployment pipeline. The domain is new and still shifting weekly. What's the risk, and what would you recommend instead?
What is a 'distributed monolith' and why is it the worst outcome?
Key takeaways
- The monolith-vs-microservices question is a false binary: the modular monolith (one deployable, enforced internal boundaries) is a third, often-best option.
- The disparaged 'monolith' is the unstructured big ball of mud; a modular monolith gets clean boundaries without the distributed tax.
- Microservices cost real complexity: network calls that fail, no cross-service ACID transactions, heavy operational overhead, and boundaries that are expensive to move.
- The distributed monolith - networked but tightly coupled services - is the worst outcome, paying microservices' cost while keeping the monolith's coupling.
- The pragmatic default is to start with a modular monolith and extract services only when a concrete scaling, team, or deployment pressure demands it.