Two Ways This Usually Goes Wrong
You have an ASP.NET Core monolith. One solution, one deploy, one database with a couple hundred tables. It works — but every feature touches three modules, the build takes forever, and you can't scale checkout without scaling billing and the catalog with it. So you decide to break it into services. Here's how that decision usually plays out.
The big-bang rewrite. A team spends eighteen months rebuilding the system service by service, in parallel with the monolith that keeps shipping features. The target moves. The two diverge. Somewhere in month fourteen, someone quietly proposes freezing the monolith "just for a quarter." Two years later you have a monolith and a half-finished distributed system, and twice the on-call.
The LLM rewrite. Newer, faster, and worse in a different way. You point a coding agent at the repo and ask it to "split this into microservices." It confidently invents service boundaries that don't match how your data is actually owned, rewrites your business logic from scratch — dropping the edge cases nobody documented — and hands you a pile of code that doesn't compile. It looks like progress. It reads like your system. It is neither.
The problem was never a shortage of code generation. It's that both approaches start by guessing where the seams are — instead of reading where they already exist.
Your Service Boundaries Are Already in the Code
A .NET monolith is not a shapeless blob. It has structure the compiler already understands, and that structure is the honest source of where the services want to split:
- EF Core entities and their relationships. The types that map to tables, and the foreign keys between them, cluster into groups that are read and written together. Those clusters are your candidate bounded contexts — not because a model thinks so, but because that's how the data is actually owned.
- DbContext boundaries. Where a
DbContextdraws itsDbSetline is a boundary the previous team already committed to. It's a strong prior for database-per-service. - The call graph. Which namespaces call which, and how densely, tells you what's cohesive and what's merely adjacent. The tight clusters stay together; the thin, occasional calls between them become your service seams — the exact places that turn into an API call or an event.
CogniDev reads all of this by parsing the code — the same structural understanding the workbench builds when you open the repo. From it, a deterministic pass maps every type to a bounded-context service: the tables it owns, the cross-context calls it makes, the sagas it participates in, and the order to peel it off. No model call is involved in drawing the boundaries. Run it twice on the same code and you get the same map. That determinism is the whole point — a migration plan you can't reproduce is a migration plan you can't trust.
Move the Code. Don't Rewrite It.
This is the part that makes the difference between a demo and a migration.
Once the map is approved, CogniDev doesn't ask a model to regenerate your services from a description. It moves your existing code into the new service projects, verbatim, and marks the handful of places where the move breaks — a call that now crosses a service boundary, a transaction that now spans two databases. Everywhere else, the ported services compile as-is, because it's your code, unchanged.
That collapses the model's job from "rewrite the entire system" down to "fill these marked seams": the cross-context calls become gateway or messaging calls, and the multi-step operation that used to be one database transaction — the classic CreateOrder that touches inventory, payment, and shipping — becomes a saga with an outbox. That's real work, and it's where a model earns its keep. But it's a few dozen seams, not a hundred thousand lines. And every one of them is grounded in the code that was already there.
Rewriting from scratch throws away the thing you most want to keep: the years of edge cases baked into working code. Moving it keeps them, and asks the model only to reconnect the pieces.
The Target: The 2026 .NET Stack, Chosen for You (But Yours to Change)
Decomposition isn't just "smaller deployables." It's a set of decisions you'd otherwise make in a dozen meetings. CogniDev targets a coherent, current default and lets you override every piece in a short questionnaire that only asks what the parser can't infer:
- .NET 10 (LTS) + .NET Aspire. A modern, supported runtime with first-class local orchestration, so the whole service graph runs on your machine on day one.
- Minimal APIs or MVC controllers, behind a YARP gateway. One edge, real routing, no bespoke Node proxy that one engineer maintains.
- EF Core, database-per-service. Each service owns its schema. No more shared 800-table database where every audit reviews the whole application.
- Wolverine for CQRS, sagas, and the outbox. Cross-service writes get transactional consistency the right way — the outbox pattern, not a distributed transaction and a prayer.
- OpenTelemetry, wired in from the start. Distributed tracing is not a phase-two nice-to-have; it's how you'll debug the thing you just built.
And the cutover is a strangler-fig order, not a flag day: services peel off one at a time, in a dependency-safe sequence, so you ship the decomposition incrementally and can stop or roll back at any step.
What You Actually Do
The whole flow lives inside the workbench, and it's built so nothing irreversible happens without your approval:
- Open the monolith. CogniDev detects the stack and builds its structural understanding — entities, DbContext boundaries, routes, the call graph — from the real code.
- Pick "Decompose to microservices." It shows up in the Modernize palette only when the evidence supports it — a data-owning monolith with entities to split along, not a thin API.
- Answer the target questions. How to draw boundaries, how granular, sync vs. async, per-service database, gateway, cutover order. Target decisions only — never facts the parser already knows.
- Review the service map and the file-level plan. Before a single line is written, you see which types land in which service, which calls become seams, and the exact set of files that will be created or moved. This is a hard review gate.
- Apply, task by task. Each service is scaffolded, your code is moved in, the seams are filled, and
dotnet buildruns on every task. Work is committed on a target branch; your source is frozen and never touched, so you always have a clean before-and-after diff. - End with proof. The run finishes with a compiling solution and a build report — not a promise that it should work, but a green build that shows it does.
Why This One Actually Ships
Nothing above is magic, and that's exactly why it works. The heavy structural lifting — where the boundaries are, what owns which data, what order to extract in — is deterministic, derived from your code, and reproducible. The model is pointed at the small, genuinely hard part: reconnecting the seams and writing the saga. You keep your working code, you review the plan before anything changes, and you get a compile at the end instead of a hopeful pull request.
That's the difference between a decomposition you can defend to your architects and a rewrite you have to babysit line by line.