Few architecture debates are as persistent as microservices vs modular monolith. Microservices are often presented as the modern default, but they carry real costs in complexity and operations. This guide explains the trade-offs and gives you a practical way to decide.
Definitions
- Monolith: one deployable application. Not inherently bad — the problem is a tangled monolith with no internal boundaries.
- Modular monolith: one deployable application divided into modules (billing, orders, users) with clear boundaries, their own data ownership and explicit interfaces between them.
- Microservices: independently deployable services, each owning its data and communicating over the network via APIs or messages.
The trade-offs
| Modular monolith | Microservices | |
|---|---|---|
| Development speed early on | Fast | Slower — more infrastructure and coordination |
| Deployment | One pipeline, one artifact | Many pipelines, versioning between services |
| Transactions and consistency | Simple local transactions | Distributed consistency, sagas, eventual consistency |
| Independent scaling | Scale the whole app | Scale each service separately |
| Team autonomy | Shared release cadence | Teams release independently |
| Operations and monitoring | Simpler | Distributed tracing, service discovery, more failure modes |
| Infrastructure cost | Lower | Higher |
When a modular monolith is the right choice
- New products and MVPs where requirements are still changing.
- One or two development teams.
- Business logic that needs strong consistency across areas.
- Limited DevOps capacity.
Martin Fowler's well-known "MonolithFirst" essay argues this case: you rarely know the right service boundaries at the start.
When microservices are worth it
- Several teams blocked by a shared release process.
- Components with very different scaling or availability needs — for example a high-volume ingestion pipeline next to a low-traffic admin area.
- Parts that need a different technology or runtime.
- Mature CI/CD, observability and on-call practices already in place.
Building a good modular monolith in .NET
- One project (or folder) per module, with internal classes by default and a small public interface.
- Each module owns its tables; no module queries another module's tables directly.
- Communicate between modules through interfaces or in-process events, not shared entities.
- Enforce boundaries with architecture tests so they do not erode over time.
- Keep modules stateless and the application horizontally scalable.
Moving to microservices when the time comes
Extract one module at a time where there is a clear benefit: independent scaling, a separate team, or a different technology. Put an API or message contract in front of it, move its data, and monitor carefully. This is the same incremental approach we recommend for legacy modernization.
A quick decision checklist
- Do independent teams need to release independently today?
- Do specific parts have genuinely different scaling or reliability needs?
- Can you operate distributed systems — tracing, retries, versioning, on-call?
- Are your domain boundaries stable and well understood?
If most answers are "no", start with — or stay with — a modular monolith.
Key takeaways: microservices trade simplicity for independence. Start with a modular monolith with strict boundaries, and extract services only when teams, scaling or technology give you a concrete reason.
Facing an architecture decision? Our software architecture consulting includes architecture reviews and fractional architect support. Also see REST API design best practices.



