Featured image of post AppHost is da new Composition Root

AppHost is da new Composition Root

Compose your ecosystem. The way you are used to.

.NET Aspire, introduced in 2024, is a modern approach to building cloud-native applications. It provides a consistent, opinionated set of tools and patterns to help you develop and operate distributed apps. One of its key features is the AppHost entry point.

Composition Root

I first encountered the concept of the Composition Root in Dependency Injection Principles, Practices, and Patterns by Mark Seemann (September 2011). In essence, a Composition Root is the single location in an application where all components are wired together. Whether you use a DI container or manually wire dependencies with Pure DI, you typically follow the Register-Resolve-Release (RRR) pattern to structure the composition flow. The Composition Root is placed as close as possible to the application’s entry point to ensure the application remains agnostic.

A Pure DI example of the RRR pattern looks like this:

1
2
3
4
5
var heraldry = new Heraldry();          // Register
using (var king = new King(heraldry))   // Resolve
{
    king.RuleTheCastle();               // Exercise
}                                       // Release

This translates to a DI container example as follows:

1
2
3
4
5
6
var container = new WindsorContainer();         // Bootstrap
container.Install(FromAssembly.This());         // Register
using (var king = container.Resolve<IKing>())   // Resolve
{
    king.RuleTheCastle();                       // Exercise
}                                               // Release

This approach offers several benefits:

  • Consistency: A single entry point for composition ensures all components are wired together consistently, reducing configuration errors.
  • Separation of Concerns: Composition logic is separated from business logic, making the codebase cleaner and easier to maintain.
  • Flexibility: You can change the composition logic without affecting business logic, allowing for easier refactoring and evolution.

AppHost

The AppHost project serves as the entry point for a .NET Aspire application. Over time, Microsoft has streamlined application building using the IHost and IHostBuilder interfaces. .NET Aspire introduces its own DistributedApplication flavor, providing a fluent API to register components and their dependencies.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
var builder = DistributedApplication.CreateBuilder(args);   // Bootstrap

var stark = builder.AddHouse("stark");                      // Register

builder.AddProject<westeros>("westeros")                    // Register
       .WithReference(stark)                                // Reference
       .WaitFor(stark);                                     // Sequence

var sevenKingdoms = builder.Build();                        // Resolve
sevenKingdoms.Run();                                        // Exercise

The terms Register and Resolve are used intentionally to mirror the RRR pattern. Release is implicit, as the DistributedApplication class manages component lifecycles automatically using IDisposable semantics. The IDistributedApplicationBuilder acts as a DI container for the distributed application, responsible for registering and wiring all components. The hypothetical AddHouse method registers a house component, while AddProject registers the westeros project with a reference to the house. The WaitFor method ensures westeros waits for stark to be ready before starting. Finally, the Build method resolves the application and returns an IDistributedApplication instance, which can be run with Run.

Analogies

All components of the distributed application are composed in a single place, which is also the application entry point. Manually registering and wiring components is reminiscent of the Pure DI approach, but it more closely resembles using a DI container with explicit registration. The RRR pattern is clearly present. But do the advantages of the Composition Root — consistency, separation of concerns, and flexibility — translate to .NET Aspire? Let’s explore.

Consistency

By using a single DistributedApplication instance to register and wire all actors in the distributed application, consistency is enforced. Everything is in one place, eliminating scattered registrations. The AppHost serves as the single source of truth for application composition and follows the established .NET configuration rules for initialization and customization.

Separation of Concerns

Only the AppHost project references the .NET Aspire SDK, ensuring business logic remains agnostic of deployment and orchestration details. If you later decide to switch from .NET Aspire to another framework, you can do so without modifying business logic. .NET Aspire handles underlying facilities such as service discovery and observability out of the box.

Flexibility

Each AddXXX method in IDistributedApplicationBuilder acts as a seam or interface, allowing you to swap implementations or add new ones without affecting the rest of the application. This enables your application to evolve as requirements or technologies change. For example, to add authentication, you might use the hosting integration to spin up a local Keycloak container.

1
2
3
4
var census = builder.AddKeycloak("census");

builder.AddProject<westeros>("westeros")
       .WithReference(census);

Later, you might switch to a shared remote Keycloak instance, for example to leverage an existing SSO Identity Provider. .NET Aspire recently introduced the AddExternalService method, allowing you to register an external service without a local containerized instance. This is especially useful in production, where you want to connect to an existing service.

1
2
3
4
var census = builder.AddExternalService("census", "https://census.westeros.com");

builder.AddProject<westeros>("westeros")
       .WithReference(census);

You can even combine both approaches and be able to switch between them easily.

1
2
3
4
5
6
var census = isDevelopment
            ? builder.AddKeycloak("census")
            : builder.AddExternalService("census", "https://census.westeros.com");

builder.AddProject<westeros>("westeros")
       .WithReference(census);

Conclusion

.NET Aspire stands out for its versatility and flexibility. Composing your distributed application in a single place — the AppHost — is a game changer. Integrating local, containerized, and remote services becomes straightforward and sustainable. Few patterns are truly one size fits all or one-shot. The Composition Root pattern is robust and flexible enough to scale with your needs. Its principles and benefits apply to both self-contained and distributed applications. Seeing a proven pattern implemented in a new framework is always reassuring as it makes you feel at home, even when exploring new territory.

Licensed under CC BY-NC-SA 4.0
Last updated on Jul 25, 2025 00:00 UTC
Built with Hugo
Theme Stack designed by Jimmy