One Codebase, Two Topologies¶
The Library walkthrough runs everything in one process. But the same domain code works when each context is its own service. The difference is one file: Program.cs.
Single Process¶
All runtimes in one builder:
var builder = WebApplication.CreateBuilder(args);
builder
.AddCatalogRuntime()
.AddLendingRuntime();
var app = builder.Build();
app.MapCatalogRestApi();
app.MapLendingRestApi();
app.Run();
Integration events flow through in-process channels. One deploy, one process, shared memory.
Separate Services¶
One Program.cs per context:
var builder = WebApplication.CreateBuilder(args);
builder.AddCatalogRuntime(rt =>
{
rt.AddCatalogStorePostgres();
rt.SubscribeFromServiceBus<LendingEvents>();
});
var app = builder.Build();
app.MapCatalogRestApi();
app.Run();
var builder = WebApplication.CreateBuilder(args);
builder.AddLendingRuntime(rt =>
{
rt.AddLendingStorePostgres();
rt.PublishToServiceBus<LendingEvents>();
});
var app = builder.Build();
app.MapLendingRestApi();
app.Run();
The domain projects (Library.Catalog, Library.Lending) are unchanged. Same commands, same handlers, same tests. Only the composition root changes.
Aspire Orchestration¶
For separate services, .NET Aspire wires the infrastructure:
var builder = DistributedApplication.CreateBuilder(args);
var serviceBus = builder.AddAzureServiceBus("messaging");
var postgres = builder.AddPostgres("postgres").AddDatabase("librarydb");
builder.AddProject<Projects.Library_Catalog_Service>("catalog")
.WithReference(serviceBus).WithReference(postgres);
builder.AddProject<Projects.Library_Lending_Service>("lending")
.WithReference(serviceBus).WithReference(postgres);
builder.Build().Run();
When to Split¶
Start with one process. It's simpler, faster to develop, and easier to debug. Split when you have evidence:
- Different deployment cadences — Lending changes weekly, Catalog changes monthly
- Different scaling needs — Catalog gets 100x more read traffic
- Different teams — the Catalog team wants to deploy without coordinating with Lending
- Compliance boundaries — patron data needs different security controls than book data
"Microservices" isn't a goal — it's a tradeoff you make when the alternative is worse. The nice part: Deepstaging doesn't force the decision upfront. Split when you're ready, not on day one.