Modular Monolith in .NET 9: Clean Boundaries Before Microservices
02 Jun 2025
.NETArchitectureModular MonolithMicroservices
A practical architecture note on using modular monoliths in .NET 9 to keep delivery simple while preparing clear boundaries for future microservices.
Modular Monolith in .NET 9: Clean Boundaries Before Microservices
Microservices are powerful, but they are not a shortcut to good architecture.
In many products, a clean modular monolith is the better starting point: one deployable system, clear internal boundaries, and fewer moving parts while the domain is still evolving. That gives a team the speed of a monolith without giving up architectural discipline.
This is the architecture I usually prefer when the product needs to move fast, but the codebase still needs room to grow.
The problem with starting distributed too early
Teams often move to microservices because they want scale, ownership, or modern architecture. Those are valid goals, but distributed systems also bring immediate cost:
- service discovery
- network failures
- versioned contracts
- distributed tracing
- deployment coordination
- cross-service data consistency
- more infrastructure than the product may need
If the domain boundaries are still unclear, microservices can freeze the wrong assumptions into separate services.
A modular monolith gives you time to learn the domain first.
What a good modular monolith looks like
The goal is not to create a large folder called Services and call it architecture.
A useful modular monolith has explicit boundaries:
- each module owns a feature area or bounded context
- module internals are not freely shared
- data access is scoped intentionally
- cross-module communication goes through contracts
- dependencies point in one direction
- tests protect boundary rules
The system still deploys as one application, but the inside of the application is organized like a product with real domains.
That structure matters because it keeps future options open. If one module later needs to become a separate service, the boundary already exists.
Patterns I lean on in .NET
For .NET systems, I usually keep the patterns simple:
- feature folders for vertical ownership
- internal classes by default
- module-level application services
- explicit request/response contracts where modules interact
- EF Core contexts scoped by module when the domain justifies it
- integration tests for important workflows
- architecture tests that prevent accidental references
This is not heavy DDD ceremony. It is practical boundary management.
The useful question is:
If this module had to move out later, how much would we need to untangle?
If the answer is "almost everything," the monolith is not modular yet.
Where .NET 9 helps
.NET 9 makes this style comfortable because the platform is already strong for production APIs:
- ASP.NET Core keeps endpoint composition lightweight
- Minimal APIs work well for focused module endpoints
- dependency injection is built in and predictable
- EF Core supports clean persistence boundaries
- background services cover internal async work
- OpenTelemetry support makes observability easier
The architecture does not need to be exotic.
Most business systems need maintainable boundaries, reliable data flow, secure APIs, and predictable deployment more than they need a complex service mesh on day one.
When microservices become worth it
A modular monolith should not become an excuse to avoid distribution forever.
I would start considering microservices when:
- one module needs independent scaling
- one team owns a domain with a separate release cadence
- the data ownership is clearly separate
- a module has different availability or security requirements
- deployment risk is better managed by splitting the service
At that point, extraction becomes an engineering decision, not an identity decision.
The best microservice candidates are usually the modules that were already well isolated inside the monolith.
Engineering takeaway
My default advice is simple:
Start modular. Stay boring. Split only when the product pain justifies the operational cost.
That is also why modular monoliths fit the way I think about full-stack product development and .NET/Azure engineering. Good architecture is not about showing off a pattern. It is about keeping the system understandable while the product keeps changing.
Related: