For most teams, a Modular Monolith is the better choice: it gives you clear boundaries and independent modules without network calls between them, distributed transactions, or the heavy operational overhead of microservices. Microservices win when you need independent scaling, independent deployments, or team autonomy at scale.
Somebody on your team is probably pushing for microservices right now. Somebody else wants to keep the monolith and skip the operational bill. Both sides usually treat it as a binary choice.
The False Dichotomy
The conversation usually starts wrong: "Should we use Microservices or a Monolith?"
That framing leaves out the middle ground, the Modular Monolith.
A Modular Monolith is a single deployable unit organized into well-defined, loosely coupled modules. Each module has its own domain model, its own data, and communicates with other modules through explicit APIs or events. If the concept is new to you, start with What Is a Modular Monolith?
It gives you most of the benefits of Microservices without the cost of running a distributed system.
What You Actually Get With Microservices
Microservices promise:
- Independent deployment
- Team autonomy
- Technology diversity
- Independent scaling
- Fault isolation
But they also bring:
- Network communication: every call can fail, be slow, or timeout
- Distributed transactions: no ACID across services, you need sagas
- Data consistency: eventual consistency everywhere
- Operational overhead: service discovery, load balancing, health checks, container orchestration
- Debugging complexity: distributed tracing, log correlation, request tracking across services
- Testing difficulty: integration tests need multiple running services
Teams running Microservices in production deal with these every day.
What You Get With a Modular Monolith
A Modular Monolith gives you:
- Module boundaries: clear separation of concerns, just like service boundaries
- Independent domain models: each module owns its data and logic
- Simple deployment: one unit to build, test, and deploy
- In-process communication: no network calls between modules
- Shared transactions when needed: ACID is available, eventual consistency is optional
- Simple debugging: one process, standard debugging tools
- Straightforward testing: integration tests run against one application
What it doesn't give you:
- Independent scaling of individual modules (but you can scale the whole monolith horizontally)
- Technology diversity (all modules share the same runtime)
- Independent deployment of individual modules
Side-by-Side Comparison
A Modular Monolith keeps its modules inside one deployable, talking in-process, while Microservices split them into separate services that talk over the network.
Here's how the two architectures compare:
| Modular Monolith | Microservices | |
|---|---|---|
| Deployment | Single unit | Per service |
| Communication | In-process method calls | Network calls (HTTP, gRPC, messaging) |
| Data consistency | Strong or eventual, your choice | Eventual consistency across services |
| Team independence | Module ownership | Full service ownership |
| Scaling | Whole application | Per service |
| Operational cost | Low | High (orchestration, service discovery, observability stack) |
| Debugging | One process, standard tools | Distributed tracing across services |
| Technology diversity | One stack for all modules | Any stack per service |
| Initial velocity | High | Lower, infrastructure setup comes first |
| Failure modes | No network failures between modules | Network failures between services on top of process failures |
When to Choose a Modular Monolith
1. Starting a new project. You don't know your domain boundaries yet. Getting service boundaries wrong in Microservices is expensive to fix. A Modular Monolith lets you discover and refine boundaries before committing to separate deployments.
2. Small to medium team (2-15 developers). The operational overhead of Microservices doesn't pay off until you have enough engineers to own and operate independent services.
3. Strong consistency requirements. Financial systems, booking systems, and inventory management have operations that need atomic transactions across modules, and those are much simpler in a monolith.
4. Limited DevOps maturity. Microservices require sophisticated CI/CD, container orchestration, service mesh, and observability infrastructure. If you don't have that, start with a Modular Monolith.
5. When performance matters. In-process method calls are orders of magnitude faster than network calls. If your modules communicate frequently, a Modular Monolith eliminates network latency entirely.
When to Choose Microservices
1. You need independent scaling. If one part of your system handles 100x more traffic than the rest, scaling just that part saves resources.
2. Large organization with autonomous teams. If you have 50+ developers across multiple teams, and each team needs to ship independently without coordination, Microservices make that possible. If you're weighing this path, start with the core microservices concepts.
3. Technology diversity is required. If one module needs Python for ML and another needs .NET for business logic, Microservices let each service use its own stack.
4. Hard fault isolation requirements. If a billing service crash must not affect the user-facing API under any circumstances, process isolation through separate services provides that guarantee.
5. You have a mature Modular Monolith. The best Microservices emerge from splitting a well-structured Modular Monolith along proven module boundaries.
The Migration Path
The path has four steps:
- Start with a Modular Monolith: define clear module boundaries, separate data per module, communicate through events
- Validate boundaries in production: see which modules need independence
- Extract individual modules when there's a concrete reason: scaling needs, team ownership, or a different deployment cadence
- Keep modules that don't need extraction: not everything needs to be a separate service
This is the Strangler Fig pattern applied at the architecture level.
If your modules communicate through the Outbox pattern and event-driven interfaces, extracting a module into a separate service is straightforward. The communication pattern stays the same, and only the transport changes, from in-process to a message broker.
What Makes a Good Modular Monolith
A Modular Monolith only works if you enforce module boundaries:
- Each module has its own
DbContext: no sharing tables across modules - Modules communicate through public APIs or events: no reaching into another module's database
- Module interfaces are explicit: define what each module exposes
- Data isolation: each module owns its database schema or database
Without enforced boundaries, a Modular Monolith degrades into a regular monolith (a Big Ball of Mud).
Common Mistakes
1. Choosing Microservices because "that's what big companies do." Netflix, Amazon, and Google have thousands of engineers and decades of infrastructure investment. Your 10-person startup does not.
2. Distributed monolith. Microservices that share databases, deploy together, and can't function independently. You pay the operational cost of microservices and keep the coupling of a monolith.
3. Modular Monolith without module boundaries. If modules access each other's database tables directly, you don't have a Modular Monolith. You have a monolith.
4. Premature extraction. Extracting a module into a Microservice before you've validated the boundary in production often means you get the boundary wrong.
Summary
Start with a Modular Monolith, define clear module boundaries, use events for inter-module communication, and enforce data isolation.
When a module needs independent scaling, deployment, or team ownership, extract it into a Microservice.
Frequently Asked Questions
Is a modular monolith better than microservices?
For most teams, yes. A modular monolith gives you clear boundaries and independent modules without network calls between them, distributed transactions, or heavy operational overhead. Microservices win when you need independent scaling, independent deployments, or team autonomy at scale.
When should you switch from a modular monolith to microservices?
Extract a module when there is a concrete driver: one module needs independent scaling, a team needs its own deployment cadence, or you need hard fault isolation. Extracting without one of these reasons adds cost with no benefit.
Can a modular monolith scale?
Yes. You scale the whole application horizontally by running multiple instances behind a load balancer. What you cannot do is scale one module independently of the others, which is usually only a problem for very uneven workloads.
What is a distributed monolith?
A distributed monolith is a system split into microservices that still share databases, deploy together, and cannot function independently. It combines the operational cost of microservices with the coupling of a monolith, and it is the most common microservices failure mode.
Do microservices always need eventual consistency?
Effectively yes. Once data lives in separate services with separate databases, you lose cross-service ACID transactions and must coordinate with sagas or the outbox pattern. In a modular monolith, strong consistency remains available when you need it.



