Skip to content

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:

Program.cs (modular monolith)
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:

Library.Catalog.Service/Program.cs
var builder = WebApplication.CreateBuilder(args);
builder.AddCatalogRuntime(rt =>
{
    rt.AddCatalogStorePostgres();
    rt.SubscribeFromServiceBus<LendingEvents>();
});
var app = builder.Build();
app.MapCatalogRestApi();
app.Run();
Library.Lending.Service/Program.cs
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:

Library.Aspire/Program.cs
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.