The Pendulum Has Swung Back
After a decade of "microservices all the things," the industry is course-correcting. Amazon Prime Video famously moved from microservices back to a monolith and reduced costs by 90%. Shopify runs one of the world's largest Rails monoliths. Basecamp ships a monolith that serves millions of users.
The lesson is not that microservices are bad. It is that architecture decisions must be driven by constraints — team size, traffic patterns, deployment requirements, and organisational structure — not by resume-driven development or conference talks.
When the Monolith Is the Right Choice
A well-structured monolith is the correct choice more often than most architects want to admit. Here are the conditions:
Team size under 30 engineers
Martin Fowler's microservice prerequisites remain valid: if your team is not large enough to staff independent teams per service (3-7 engineers each), the coordination overhead of microservices exceeds the independence benefits. A team of 8-15 engineers running a monolith can iterate faster than the same team running 10 microservices.
Domain is not well understood
If you are building a new product and the domain boundaries are unclear, starting with microservices means you will draw the wrong service boundaries. Refactoring across service boundaries (changing APIs, migrating data, updating contracts) is 10-50x more expensive than refactoring within a monolith. Start with a modular monolith and extract services when the boundaries are battle-tested.
Traffic is uniform
If all parts of your application receive similar traffic and have similar scaling requirements, the primary scaling advantage of microservices — independently scaling hot services — does not apply.
The Modular Monolith: Best of Both Worlds
A modular monolith gives you clean module boundaries, independent development workflows, and the ability to extract services later — without the distributed systems complexity:
// Modular monolith structure (NestJS example)
src/
modules/
billing/
billing.module.ts
billing.service.ts
billing.controller.ts
interfaces/ // Public interface for other modules
billing.interface.ts
users/
users.module.ts
users.service.ts
interfaces/
users.interface.ts
orders/
orders.module.ts
orders.service.ts
interfaces/
orders.interface.ts
shared/ // Shared kernel (minimal)
database/
events/ // In-process event bus
app.module.ts
Key rule: Modules communicate only through their public interfaces, never by directly accessing each other's database tables or internal services. This makes future extraction into a separate service straightforward.
When Microservices Are the Right Choice
Multiple teams need independent deployment
The primary driver for microservices is organisational, not technical. When you have 5+ teams that need to deploy independently without coordinating merge/release schedules, microservices provide genuine value. This typically happens at 30-50+ engineers.
Wildly different scaling requirements
If your image processing pipeline needs 100x the compute of your user management service during peak hours, independent scaling provides real cost savings.
Different technology requirements per component
If one component genuinely benefits from a different language, runtime, or database (e.g., a real-time analytics engine in Rust alongside a CRUD API in Node.js), microservices enable polyglot architectures.
Regulatory isolation
When different parts of your system have different compliance requirements (e.g., PCI DSS for payment processing), isolating them into separate services with separate deployment pipelines and access controls simplifies compliance.
The True Cost of Microservices
Before choosing microservices, honestly assess whether you can pay these costs:
| Cost Category | Monolith | Microservices |
|---|---|---|
| Deployment complexity | One pipeline, one deployment | N pipelines, orchestrated deployments |
| Debugging | Single process, stack traces work | Distributed tracing required (Jaeger, Zipkin) |
| Data consistency | ACID transactions | Eventual consistency, saga patterns |
| Local development | Run one process | Docker Compose with 10+ services |
| Infrastructure cost | 1 load balancer, 1 cluster | Service mesh, API gateway, message broker |
| Testing | Unit + integration tests | + contract tests + chaos tests |
| Operational overhead | Low | Requires mature DevOps (Level 3+ maturity) |
According to InfoQ's analysis, the infrastructure and operational overhead of microservices adds 30-50% to the total cost of ownership compared to an equivalent monolith for teams under 50 engineers.
Decision Framework
Answer these five questions honestly:
- How large is your engineering team? Under 30: start with monolith. Over 50: consider microservices.
- How well-defined are your domain boundaries? If you are still figuring them out, monolith. If they are stable for 2+ years, microservices are viable.
- What is your DevOps maturity? Below Level 3: monolith. You cannot operate distributed systems without standardised CI/CD, IaC, and observability.
- Do different components have genuinely different scaling needs? If yes, that is a real argument for microservices. If no, you lose the primary technical benefit.
- Can you afford the infrastructure overhead? Service mesh, API gateway, distributed tracing, contract testing — these are not optional in a microservices architecture.
The Migration Path: Monolith to Microservices
If you start with a monolith and later need microservices, here is the path:
- Modular monolith: Enforce module boundaries within the monolith
- Strangler fig: Extract the first service behind an API gateway while the monolith handles everything else
- Extract by bounded context: Move one bounded context at a time, starting with the one that benefits most from independence
- Event-driven decoupling: Replace synchronous inter-module calls with events before extraction
This incremental approach is far lower risk than a big-bang rewrite. Our legacy modernisation service follows this exact pattern, and our development team can help you build the right architecture from day one. Get in touch for an architecture review.