.NET Monolith to Microservices: When (and When Not)
Moving a .NET Framework monolith to modern .NET upgrades the platform but not the architecture — a lifted monolith is still a monolith. Whether to decompose it into microservices depends on real pressure, not fashion: a component that must scale independently, teams blocked on one release train, or a failure that must be isolated. Where no such pressure exists, a modular monolith on modern .NET — one deployable with strong internal boundaries — is usually the better destination, keeping in-process speed and transactions while gaining clean structure. When decomposition is warranted, extract one service at a time behind a facade, proving each slice before cutover, and stop when the pressure is relieved.
Parts 2 through 6 get a .NET Framework application onto modern .NET. That is a genuine win — a supported, fast, actively developed platform. But it is worth saying plainly, because the relief of finishing the platform move hides it: landing on modern .NET does not make a monolith well-architected. A monolith that has been carefully lifted onto .NET 8 is a modern-.NET monolith. If it was a tangle on Framework, it is a tangle on modern .NET.
So the platform move surfaces a second, separate question: should this application be decomposed into microservices? This part answers it for the .NET context — and the honest answer is “sometimes, and less often than the architecture diagrams suggest.”
Two decisions, deliberately kept apart
The most important move in this part is to refuse to bundle two decisions that feel like one:
- The platform decision: Framework → modern .NET. (Parts 2–6.)
- The architecture decision: monolith → services.
They are independent. You can modernize the platform and keep a monolith. You can have a microservices mess on either platform. Bundling them — “while we’re moving to .NET 8, let’s also break it into microservices” — is one of the most expensive mistakes in this whole space, because it changes the platform and the architecture in one move, so when something breaks you cannot tell which change caused it, and the blast radius is the entire system at once.
Modernizing the platform and decomposing the architecture are two projects; doing them as one is not twice the work, it is several times the risk.
When decomposition is actually warranted
Microservices are the right tool for specific, nameable pressures — the same ones that apply to any stack, here in their .NET form:
- Independent scaling. One part of the system — a pricing engine, a search path, a document processor — consumes most of the compute, and you want to scale just it rather than cloning the whole app. On a per-request cloud bill, this is real money.
- Team autonomy. Several teams are blocked on one shared release train, and you want them to deploy on their own cadence. This pressure scales with organization size, and it is the one most often mistaken for a technical need.
- Fault isolation. One component’s failure must not take down the others — the recommendations service falling over should not stop checkout.
Each is a specific answer to a specific problem. If you can name the pressure and point to it, extraction is justified. If the only reason is “microservices are modern,” there is no pressure, and you are about to buy network latency, distributed-data complexity, and operational overhead in exchange for nothing. The full decision framework is in when to break up a monolith.
The destination most .NET teams actually want: the modular monolith
The binary “monolith or microservices” hides the option that is right for the majority of modernized .NET systems: the modular monolith.
It is a single modern .NET application — one build, one deploy, in-process calls, real Entity Framework Core transactions — divided internally into well-bounded modules, each with an explicit public interface and no reaching into another module’s internals or tables. It has the design discipline of microservices without their network and operational cost. In .NET this is comfortable to express and enforce: separate projects per module, dependency rules between them, public contracts at the boundaries, with the option of architecture tests that fail the build when a boundary is violated.
This matters for two reasons. First, most of the pain teams blame on “being a monolith” is not about deployment topology at all — it is about a tangled monolith with no internal boundaries. Drawing those boundaries fixes the pain without a single network hop. Second, a modular monolith is the correct first destination even if you do intend to extract services later: once the boundaries are clean in-process, pulling a module out into a service is a mechanical move rather than an archaeology project.
For a freshly modernized .NET app, the modular monolith is very often the right place to stop.
There is a sequencing payoff here, too. The platform migration in Parts 2–6 already pushed you toward modular boundaries: extracting logic out of Web Forms code-behind and WCF implementations into clean libraries is the boundary-drawing work, done for a different reason. A team that modernized carefully arrives at the architecture decision already most of the way to a modular monolith — the libraries are separated, the contracts are explicit, the data access is consolidated. Recognizing that you are already there, rather than reflexively reaching for services, is often the highest-value architectural call in the whole program. The expensive instinct is to treat “we finished the platform move” as a starting gun for decomposition; the disciplined move is to ask whether decomposition buys anything at all.
When you do decompose: one service at a time
Where a real pressure justifies extraction, it is done with the same incremental discipline as everything else in this series, and it is the subject of the monolith-to-microservices migration playbook:
- Draw the boundary along the domain, not along technical layers — the unit you extract should own a coherent slice of the business and its data.
- Extract one service, behind the same facade pattern, with an anti-corruption layer at the seam so the new service is not shaped by the monolith’s internal model.
- Prove parity — the extracted service must behave identically to the in-monolith code it replaces before it carries live traffic.
- Cut over gradually, with rollback a route change away.
- Stop when the pressure is relieved. This is the discipline most teams miss. Extract the components under genuine pressure, and leave the rest in the now-clean modular monolith. A finished decomposition is measured by the pain removed, not the number of services created.
The trap to avoid: the distributed monolith
The worst outcome is not staying a monolith; it is becoming a distributed monolith — services split apart but still so tightly coupled that they must be deployed together, share a database, and chain synchronous calls across the network. You pay the full cost of microservices (latency, partial failure, operational overhead) and get none of the benefit (independence). It is a common landing spot for teams that decomposed by fashion rather than pressure, and it is detailed in the distributed monolith anti-pattern. The defense is the same throughout: extract only along real domain boundaries, only where a pressure justifies it, and give each service its own data.
Architecture is a trade, not a trophy
Plenty of valuable .NET systems should remain monoliths forever — well-structured, modular, on modern .NET, and entirely correct as one deployable. Equally, some genuinely need a handful of services, and the teams running those are solving real problems. The mistake is treating service count as a score. Architecture is a trade matched to measurable pressure, not a trophy. For a modernization specifically, the safest default is: get onto modern .NET as a modular monolith first, then extract services only where you can name the pressure — and not before.
Where this leads
That completes the toolkit: the platform move, the hard rebuilds, the tooling, and the architecture decision. The final part puts them together. Part 8, A .NET Modernization, End to End, walks one composite system — Framework, WebForms, WCF, a VB.NET service, a tangled monolith — through the whole method from first assessment to a retired Framework app, showing how the pieces of this series fit into a single program.
Frequently asked questions
- Should a .NET monolith be split into microservices during modernization?
- Only where there is a specific, nameable pressure that microservices solve — a component that needs to scale on its own, multiple teams blocked by a single release train, or a failure that must be isolated from the rest of the system. Modernizing the platform (Framework to modern .NET) and decomposing the architecture (monolith to services) are two separate decisions, and bundling them adds enormous risk for no necessary reason. If you cannot name the pressure, a modular monolith is the better target.
- What is a modular monolith in .NET?
- A modular monolith is a single modern .NET application that deploys as one unit but is divided internally into well-bounded modules, each with an explicit interface and no reaching into another module's internals or data. It gives you the design discipline of microservices — clear domains, decoupled components — while keeping in-process calls, real database transactions, and one deployment to operate. In .NET it is straightforward to enforce with project structure and dependency rules, and it is the ideal first destination and the natural launch point if you later extract services.
- Can you migrate to .NET and microservices at the same time?
- You can, but doing both at once multiplies the risk, because you are changing the platform and the architecture in the same move and lose the ability to tell which change caused a problem. The lower-risk sequence is to get onto modern .NET first as a modular monolith, then extract only the services that a real pressure justifies, one at a time. Each extraction is then a clean architectural change on a stable platform, proven by parity before it carries traffic.