Microservices have a marketing problem: they're associated with the engineering cultures of Netflix and Amazon, so ambitious teams assume adopting them is what serious companies do. But those companies moved to microservices to solve problems of enormous scale and huge headcount — problems you almost certainly don't have yet. For most products, splitting too early is one of the most expensive mistakes you can make. Here's the honest trade-off.
What a monolith actually gives you
A monolith is one deployable application. That simplicity is a feature, not a limitation, especially early:
- One codebase, one deploy. No orchestration, no service mesh, no distributed tracing just to understand a request.
- Simple debugging. A stack trace crosses your whole request. You're not correlating logs across five services to find one bug.
- Fast local development. Run the whole app on your laptop and iterate.
- Easy transactions. Data consistency is a database transaction, not a distributed saga you have to design and get right.
The modern version isn't a big ball of mud. A modular monolith enforces clean internal boundaries — separate modules with clear interfaces — giving you much of the organization of microservices with none of the network overhead.
What microservices actually cost
Splitting into services doesn't remove complexity; it moves it from your code into the network, where it's harder to see and reason about. You inherit a long list of new problems:
- Distributed systems failure modes — partial failures, retries, timeouts, and eventual consistency become your daily reality.
- Data consistency across services — no more easy transactions; you're designing sagas and compensating actions.
- Operational overhead — every service needs deployment, monitoring, logging, and on-call.
- Slower local development and debugging — reproducing a bug can mean running half your architecture.
For a small team, this overhead can consume the very velocity you were trying to gain.
When microservices genuinely help
They're not a mistake — they're a solution to specific problems that appear at scale:
- Independent scaling. One component (video encoding, say) needs far more resources than the rest, and you want to scale it alone.
- Team autonomy at size. Many teams stepping on each other in one codebase; clear service ownership lets them deploy independently.
- Genuinely different requirements. A component needs a different language, runtime, or release cadence than the rest.
Notice the pattern: these are problems of scale and organization, not of building a product. If you don't have them yet, you're paying the cost without the benefit.
The pragmatic path
Start with a modular monolith. Keep clean module boundaries so the code is well-organized and — crucially — so it's splittable later. When a real, measured need appears (a component that must scale independently, a team that needs to deploy on its own cadence), extract that one service along an existing module boundary. Let the architecture evolve to meet actual pressure rather than anticipated pressure.
This incremental path gives you the best of both: the speed of a monolith now, and a clean route to services if and when you truly need them. Most successful products run a well-structured monolith far longer than the internet would have you believe.
If you're weighing a split and want a second opinion before committing, let's talk.
Originally published on the Doktouri Agency blog. We build web, mobile, SaaS, and AI products — let's talk.
Top comments (0)