Skip to content

Module System

The Module System pattern organizes an application into self-contained units (modules), each owning its own service registration and initialization lifecycle. A central loader resolves startup order through topological sorting of declared dependencies, guaranteeing that a module never starts before its prerequisites.

Granit implements a variant inspired by the ABP framework (ASP.NET Boilerplate), adapted for an ecosystem of independent NuGet packages.

flowchart TD
    A[Application Host] -->|"AddGranit(TRootModule)"| B[ModuleLoader]
    B -->|1. Discovery| C["Recursive traversal of<br/>[DependsOn] attributes"]
    C -->|2. Graph| D["Build dependency<br/>DAG"]
    D -->|3. Topological sort| E["Kahn's algorithm<br/>(cycle detection)"]
    E -->|4. ConfigureServices| F["Module A, Module B, ..., Root<br/>(topological order)"]
    F -->|5. OnApplicationInitialization| G["Module A, Module B, ..., Root<br/>(same order)"]

    style B fill:#4a9eff,color:#fff
    style E fill:#ff6b6b,color:#fff
ComponentFileRole
GranitModulesrc/Granit.Core/Modularity/GranitModule.csAbstract base class: ConfigureServices(), ConfigureServicesAsync(), OnApplicationInitialization(), OnApplicationInitializationAsync()
DependsOnAttributesrc/Granit.Core/Modularity/DependsOnAttribute.csDeclares module dependencies via [DependsOn(typeof(...))]
ModuleLoadersrc/Granit.Core/Modularity/ModuleLoader.csTopological sort (Kahn’s algorithm) with circular dependency detection
ModuleDescriptorsrc/Granit.Core/Modularity/ModuleDescriptor.csModule metadata (type, instance, dependencies)
GranitApplicationsrc/Granit.Core/Modularity/GranitApplication.csFull lifecycle coordinator
AddGranit<TModule>()src/Granit.Core/Extensions/GranitHostBuilderExtensions.csEntry point for the host application

In-house variant — Dual Sync/Async: the async hooks (ConfigureServicesAsync, OnApplicationInitializationAsync) delegate to their sync counterpart by default. A module can override either one without being required to implement both.

ProblemSolution
Independent NuGet packages that need to self-configureEach package exposes a GranitModule with its own ConfigureServices()
Unpredictable initialization order with native DITopological sort guarantees dependencies are registered first
Silent circular dependenciesKahn’s algorithm throws an explicit exception listing the involved modules
Code duplication in application Program.cs filesA single builder.AddGranit<MyAppModule>() call replaces dozens of lines
// Declaring an application module
[DependsOn(typeof(GranitPersistenceModule))]
[DependsOn(typeof(GranitWolverineModule))]
[DependsOn(typeof(GranitFeaturesModule))]
public sealed class MyAppHostModule : GranitModule
{
public override void ConfigureServices(ServiceConfigurationContext context)
{
ServiceCollection services = context.Services;
services.AddScoped<IPatientService, PatientService>();
}
public override void OnApplicationInitialization(ApplicationInitializationContext context)
{
WebApplication app = context.GetApplicationBuilder();
app.MapHealthChecks("/healthz");
}
}
// Entry point -- a single line
WebApplicationBuilder builder = WebApplication.CreateBuilder(args);
builder.AddGranit<MyAppHostModule>();
WebApplication app = builder.Build();
await app.UseGranitAsync();
app.Run();