Monolith vs Microservices: The Decision, Not the Dogma
Microservices are sold as the grown-up architecture and monoliths as the thing you outgrow. The truth is less flattering to both. Here's how to decide by what your problem actually needs — and why most teams should start with a monolith.
Somewhere along the way, microservices became the default "serious" architecture and monoliths became the thing you apologize for. That framing has caused more damage than almost any other trend I've watched — teams of five splitting a simple app into fifteen services and drowning in the operational overhead of a problem they didn't have. The real answer isn't a side; it's a decision, and it hinges on what you're actually optimizing for.
Define the terms honestly
- A monolith is one deployable unit. All the code ships together, runs in one process, and usually talks to one database. "Monolith" is not a synonym for "messy" — a well-structured monolith has clean internal module boundaries.
- Microservices split the app into many small, independently deployable services, each owning its own data and communicating over the network.
The key word is independently deployable. That's the property you're buying, and everything you pay flows from it.
What microservices actually give you
They solve real problems, but organizational ones more than technical ones:
- Independent deploys. Teams ship their service without coordinating a release with everyone else. At 200 engineers, this is enormous; at 5, there's nothing to coordinate.
- Independent scaling. Scale the one hot service instead of the whole app. Genuinely useful when one component's load dwarfs the rest.
- Fault isolation. A crash in one service needn't take down the others — if you designed the boundaries and fallbacks for it.
- Technology freedom. Different services in different languages. Occasionally worth it, often just fragmentation.
What they cost — and it's not small
Every one of those benefits is paid for in complexity that a monolith simply doesn't have:
- The network is now in the middle of your app. In-process function calls become network calls that can be slow, fail, or arrive twice. You inherit every distributed-systems problem — retries, timeouts, partial failure, idempotency.
- Data is scattered. No more joins across a single database, no more one transaction spanning the operation. Consistency becomes eventual, and you're writing sagas to coordinate what was one
COMMIT. - Operational surface explodes. Fifteen services means fifteen deploys, dashboards, logs, and on-call surfaces. Debugging a request means tracing it across service boundaries.
Start with a monolith — almost always
My default advice: start with a well-structured monolith. Early on you don't yet know your true domain boundaries, and those boundaries are exactly what a good service split depends on. A monolith lets you move fast, refactor boundaries cheaply (they're just module edges, not network contracts), and ship without operational overhead you can't yet afford.
The trick is to build it modular — clear internal seams between domains, minimal coupling — so that if you later need to extract a service, the boundary is already there to cut along.
When to actually split
Extract a service when a specific pressure demands it, not preemptively:
- A team is big enough that coordinating deploys is real friction.
- One component has scaling needs so different from the rest that coupling them is wasteful.
- A part of the system is stable and well-understood enough that its boundary is obvious and unlikely to move.
Then extract that one piece — not everything. Most successful microservice systems I've seen grew out of a monolith one deliberate extraction at a time, guided by pain, rather than being born as fifteen services on a whiteboard.
The real principle
The question was never "monolith or microservices." It's "what is the smallest architecture that solves the problem I actually have?" For most teams most of the time, that's a clean monolith. Microservices are a solution to problems of organizational scale and independent deployment — powerful when you have those problems, pure cost when you don't. Match the architecture to the pressure, not to the trend.