The monolith vs microservices debate has produced more heat than light over the past decade. Both sides have oversimplified. “Monoliths don’t scale” is demonstrably false — StackOverflow ran a monolith for years serving massive traffic. “Microservices are only for large teams” ignores the many small teams that have shipped them successfully.
The real question is not which architecture is better in the abstract. It’s which architecture fits your current constraints: your team size, your organizational structure, your operational maturity, and your actual scaling requirements.
What Microservices Actually Buy You
Microservices are not primarily a scalability solution. They are an organizational and operational strategy.
Independent deployment: teams can deploy their service without coordinating with other teams. This is the primary benefit for most organizations adopting microservices.
Independent scaling: services can scale independently. If your recommendation engine needs 10× the compute of your user service, you can scale them separately.
Technology isolation: different services can use different languages, databases, and frameworks where that’s genuinely useful.
Fault isolation: a bug in the notification service can’t crash the order service (though it can cause cascading failures if not designed carefully).
These are real benefits. They come at a real cost.
The Costs That Are Usually Understated
Distributed systems complexity. Every cross-service call introduces network failures, timeouts, partial failures, and the need for circuit breakers, retries, and idempotency. A monolith that calls a method can catch an exception. A microservice that calls another service has to handle network timeouts, authentication, versioned APIs, and service discovery.
Operational overhead. Each service needs its own CI/CD pipeline, monitoring, alerting, deployment infrastructure, and runbooks. A 10-service architecture has 10× the operational surface area of a monolith.
Distributed transactions. When a user places an order, the order service, inventory service, and payment service all need to update their data. In a monolith, this is a database transaction. In microservices, there is no transaction. You need sagas, eventual consistency, compensating actions, and careful design to maintain data consistency.
Observability. Debugging a slow request in a monolith means looking at a profile. Debugging a slow request in a microservices architecture means correlating traces across multiple services, checking each service’s metrics, and understanding the interaction between them.
Testing. Integration testing a monolith means spinning up one process. Integration testing microservices means either spinning up the entire ecosystem (slow, fragile) or mocking dependencies (which may not reflect production behavior).
Conway’s Law and the Real Driver
Conway’s Law: “Any organization that designs a system will produce a design whose structure is a copy of the organization’s communication structure.”
This cuts both ways. If your organization has five independent teams, microservices match the organizational structure — each team owns a service and deploys independently. If your organization has one team, microservices impose a distributed systems tax for no organizational benefit.
The team structure question should come before the architecture question. Microservices make deployment independent at the service level. That’s only valuable if different teams need to deploy independently. If everyone is on the same team, the benefit disappears while the cost remains.
When a Monolith Is Correct
Small teams (< 8 engineers): the overhead of maintaining multiple services exceeds the benefit of independence. One team can coordinate a monolith deployment.
Early-stage products: the domain model is not yet stable. Service boundaries that made sense in month 3 may be wrong by month 12. A monolith is easier to refactor than a distributed system.
Simple domains: not every application has complex enough domain logic to benefit from service decomposition. A CMS, a simple e-commerce site, an internal tool — these may be better as modular monoliths.
Uncertain scaling requirements: don’t design for scaling problems you don’t have. Premature service extraction optimizes for a load profile that may never materialize.
When Microservices Are Justified
Multiple independent teams: this is the primary valid reason. If Team A and Team B need to deploy independently without coordinating, services make that possible.
Genuinely different scaling requirements: the search service needs 100 instances; the admin service needs 1. Independent scaling saves money and reduces operational noise.
Technology diversity is necessary: the data science team writes Python. The core platform team writes Java. Service boundaries let them work independently.
Specific fault isolation requirements: when one component failing must not affect others, service boundaries provide that isolation (with careful circuit breaker design).
Organizational mandate: if you’re joining an existing microservices architecture, you’re joining a microservices architecture. The right response is to make it work well, not to re-litigate the architectural choice.
The Modular Monolith: The Sensible Starting Point
There’s an architecture between “big ball of mud” and “full microservices” that most discussions skip: the modular monolith.
A modular monolith has clear module boundaries enforced by the build system, each module owning its own data and exposing a defined public API. It deploys as a single unit but is internally organized as if it were microservices.
The modular monolith provides:
- Clear domain boundaries
- Independent testability of modules
- A migration path to microservices when genuinely needed
Without:
- Network calls between modules
- Distributed transaction complexity
- Multiple deployment pipelines
- Distributed tracing requirements
Start here. Extract services when there’s a concrete organizational or scaling reason to do so.
Migration Strategies
If you’re migrating an existing monolith to microservices (or the reverse), the approach matters.
Strangler Fig pattern: build the new service alongside the existing code. Route traffic to the new service incrementally. Retire the old code when the new service handles all traffic.
┌─────────────────────────────────────────────────┐
Request → Router/Proxy → Old Monolith (shrinking) │
↘ New Inventory Service (growing) │
│
This is lower risk than a rewrite because you can validate the new service handles production traffic before decommissioning the old code.
Anti-Corruption Layer: when the new service has a different domain model than the old code, translate at the boundary rather than importing the old model.
Data decoupling last: split the code before splitting the database. Running two services against the same database is suboptimal but allows you to validate service behavior before tackling the harder problem of data ownership.
The Scaling Question
“We need microservices to scale” is usually the wrong frame. Specific questions to ask:
- What specifically needs to scale? (Read traffic? Write throughput? Specific CPU-intensive operations?)
- Have we profiled the actual bottleneck?
- Can we scale vertically first?
- Can we scale horizontally with replicas of the monolith?
For most applications at most scales, a properly configured monolith running on appropriate hardware handles more load than the teams building it can generate. Instagram had 13 employees when it was acquired by Facebook and served 30 million users. It was not a microservices architecture.
Service decomposition is worth it when specific services have specific scaling requirements that cannot be addressed by scaling the monolith as a unit. Not before.
The Honest Trade-off
Monolith advantages: simpler, cheaper to operate, easier to debug, easier to deploy, easier to test, easier to refactor.
Microservices advantages: organizational independence, independent scaling, technology diversity, fault isolation.
Microservices costs: distributed systems complexity, operational overhead, observability requirements, testing complexity.
Choose based on which advantages matter most for your current situation, not based on which architecture sounds more sophisticated. Sophistication is not the goal. Shipping software that works, at an appropriate operational cost, is.