Microservices are one of the most oversold architectural patterns in software engineering. Not because they're bad — they solve real problems brilliantly when applied in the right context. But they're frequently adopted by teams who are solving the wrong problem, or who underestimate the operational complexity they're taking on.
This post gives you a clear decision framework: when microservices make genuine sense, when they don't, and what signals to look for in your own system before committing to the architecture.
What microservices actually solve
Microservices decompose a system into independently deployable services, each owning a specific business domain. Done well, this gives you:
- Independent scaling — scale the payment service without scaling the authentication service.
- Team autonomy — different teams own and deploy different services without stepping on each other.
- Fault isolation — a failure in the recommendation engine doesn't take down checkout.
- Technology flexibility — use the right tool for each service rather than being locked into one stack.
- Independent deployment — deploy changes to one service without touching others.
Notice what's on that list: team problems as much as technical problems. Microservices are often as much about organizational structure as they are about software architecture. Conway's Law applies — your system architecture will mirror your team structure. If you have one team, microservices may give you the architecture of a large organization without the benefit.
The real cost: what most teams underestimate
Microservices don't reduce complexity — they redistribute it. The complexity that used to live in a single process now lives in the network between services, in deployment orchestration, in distributed tracing, and in data consistency across service boundaries.
- Distributed systems complexity — network failures, latency, partial failures, and retry logic all become your problem.
- Data consistency — you can't use database transactions across services. Eventual consistency and saga patterns add real development overhead.
- Service discovery and networking — you need a service mesh or equivalent. This is a non-trivial operational investment.
- Observability — distributed tracing across 10 services is significantly harder than profiling a monolith. Without good tooling, debugging production incidents becomes very slow.
- Testing — integration testing across service boundaries is harder than testing a monolith. Contract tests help but add complexity.
- Kubernetes operational overhead — if you're running microservices on Kubernetes (which you probably are), you're now managing a significant platform. Upgrades, certificate rotation, capacity planning, and incident response are all your responsibility.
the hidden cost
Teams migrating to microservices frequently report that their velocity decreases for 6–12 months before improving. The operational overhead is real and front-loaded. Factor this into your decision.
The case for starting with a monolith
Martin Fowler's concept of the "majestic monolith" deserves more respect than it gets. A well-structured monolith with clear module boundaries can deliver most of the maintainability benefits of microservices without the distributed systems tax.
The key word is structured. A modular monolith — one where bounded contexts are enforced architecturally even within a single process — gives you:
- Fast local development and testing
- Simple deployment and rollback
- Database transactions across your whole domain
- Easy refactoring of service boundaries before you've committed to them
- A lower operational burden for small to medium teams
The strangler fig pattern lets you extract microservices from a monolith incrementally, once you understand your domain boundaries well enough to draw them correctly. This is almost always a better approach than designing microservices upfront before you understand your system's natural seams.
A clear decision framework
Ask these questions before committing to microservices:
| Question | Suggests monolith | Suggests microservices |
|---|---|---|
| Team size | Under 10 engineers | Multiple teams of 5–8 that need autonomy |
| Scaling requirements | Uniform — the whole system needs to scale together | Differential — specific components need independent scaling |
| Domain clarity | Boundaries are still being discovered | Domain boundaries are well-understood and stable |
| Deployment frequency | Weekly or monthly releases | Multiple independent teams deploying daily |
| Operational maturity | Limited DevOps capability | Strong DevOps, existing Kubernetes experience |
| Data consistency needs | Strong consistency required across operations | Eventual consistency is acceptable for most operations |
Anti-patterns to avoid
- Microservices for a 3-person startup. You'll spend more time on infrastructure than product. Start with a monolith, modularize, extract services when the pain of staying monolithic is real.
- Premature decomposition. Drawing service boundaries before you understand your domain almost always results in the wrong boundaries. Wrong service boundaries are extremely expensive to fix once each service has its own data store.
- Distributed monolith. Services that are independently deployed but tightly coupled — sharing a database, or making synchronous calls in chains — give you all the operational complexity of microservices with none of the benefits.
- Nano-services. Breaking down too granularly creates an explosion of inter-service communication. A service that has one endpoint and calls five other services is a design smell.
- Ignoring data management. Each service should own its data. If two services are sharing a database table, they're not actually separate services.
If you need to move from a monolith
The strangler fig pattern is the most proven approach. You build new functionality as services alongside the existing monolith, then progressively migrate existing functionality to services, until the monolith can be retired.
Start with the seams that are already natural: a service that sends emails, a service that handles authentication, a reporting service. These have clear boundaries, low coupling to the core domain, and the failure cost of getting the boundary wrong is low.