The industry conversation around architecture tends to move in cycles. Microservices were positioned for years as the default answer for any system expecting growth. More recently, the pendulum has swung back, with a growing number of engineering teams publicly documenting their return to monolithic or modular monolith architectures. Neither swing is really the right way to think about the decision.
The honest answer is that the right architecture depends on organizational shape and operational maturity as much as it depends on technical requirements. Here is a framework for thinking through it that avoids defaulting to whichever pattern is currently fashionable.
Start with team topology, not technology
Microservices solve an organizational problem as much as a technical one: they let independent teams ship independently without coordinating every release. If a company has one team, or a handful of tightly coordinated teams, that benefit mostly does not apply, and the operational cost of running a distributed system, service discovery, network reliability, distributed tracing, gets paid without the corresponding payoff.
A useful rule of thumb: if adding a service boundary would not let two teams stop talking to each other during a release, the boundary is probably not earning its complexity yet.
Deployment independence versus deployment overhead
The core promise of microservices is independent deployability. But independent deployability comes with a tax: every service needs its own CI/CD pipeline, its own monitoring, its own on-call rotation awareness, and its own dependency management. For a small engineering team, this tax often exceeds the benefit, because the team ends up maintaining infrastructure complexity that scales with the number of services, not with the number of engineers available to manage them.
A modular monolith aims to get part of the benefit, clear internal boundaries, enforced module contracts, without paying the full distributed systems tax. Modules can be extracted into separate services later, once a specific one demonstrably needs independent scaling or independent deployment cadence, rather than speculatively upfront.
Data ownership is the part teams underestimate
The hardest part of a microservices migration is rarely the API layer. It is deciding which service owns which piece of data, and handling the cases where two services need consistent views of related data without a shared database. This forces teams into patterns like event sourcing, sagas, or eventual consistency, all of which are genuinely more complex to reason about than a single transactional database.
Teams that skip this analysis and split services along whatever boundaries seemed convenient at the time tend to end up with a distributed monolith: multiple deployable units that are still tightly coupled through shared data dependencies, which delivers the worst of both worlds, operational overhead without genuine independence.
Signals that favor microservices
- Multiple teams that need to release on different schedules without blocking each other
- Components with meaningfully different scaling profiles, where one piece needs to handle ten times the load of another
- Regulatory or security requirements that genuinely require isolation between components
- A team large enough to staff dedicated platform or infrastructure roles to absorb the operational overhead
Signals that favor a modular monolith
- A single team or a small number of closely coordinated teams
- Uncertain or evolving domain boundaries, where splitting services early would lock in assumptions that are likely to change
- Limited operational capacity for managing distributed systems infrastructure
- A product still finding product-market fit, where the cost of premature architectural rigidity outweighs the benefit of service independence
The migration path that tends to work
Rather than choosing definitively at the start, a workable pattern is to build a modular monolith with strict internal module boundaries, enforced through code structure and, where possible, tooling that prevents modules from reaching into each other's internals. When a specific module later demonstrates a genuine need for independent scaling or independent deployment, extracting it into a separate service is a much smaller and lower-risk change than migrating an entire system at once.
This approach treats the monolith versus microservices question not as a one-time architectural bet, but as a sequence of smaller, reversible decisions made as real operational signals appear, rather than as predictions made in advance.
Top comments (0)