Application Model in ConnectSoft Templates¶
Purpose & Overview¶
The Application Model is the central orchestration layer of ConnectSoft Templates (Microservice, API Gateway, Identity, Auth Server, etc.). It provides a comprehensive set of extension methods for dependency injection registration, middleware pipeline configuration, and cross-cutting concerns integration. The ApplicationModel serves as the composition root that wires together all layers and features of the application into a cohesive, production-ready solution.
Why Application Model?¶
The Application Model offers several key benefits:
- Centralized Configuration: Single entry point for all service registrations and middleware setup
- Clean Separation: Keeps Application project minimal—all orchestration logic in ApplicationModel
- Consistency: Standardized patterns for registering services and configuring middleware
- Modularity: Feature-based extension methods enable optional feature inclusion
- Testability: Easy to override and test individual components
- Maintainability: Clear organization makes it easy to understand and modify
- Conditional Compilation: Feature flags enable optional feature inclusion at compile time
Application Model Philosophy
The ApplicationModel is the composition root of ConnectSoft templates. It orchestrates all layers, features, and cross-cutting concerns without containing business logic. This separation ensures the Application project remains minimal and focused on hosting, while ApplicationModel handles all the complexity of wiring components together.
Architecture Overview¶
Application Model Position in Clean Architecture¶
Application (Host)
├── Program.cs (Entry Point)
└── Startup.cs (Minimal - delegates to ApplicationModel)
↓
ApplicationModel (Orchestration Layer)
├── DI Registration (ConfigureServices)
├── Middleware Pipeline (Configure)
└── Endpoint Mapping
↓
All Layers
├── DomainModel
├── PersistenceModel
├── MessagingModel
├── ServiceModel
└── Cross-Cutting Concerns
Project Structure¶
ConnectSoft.{TemplateName}.ApplicationModel/
├── {TemplateName}RegistrationExtensions.cs (Main orchestration)
├── OptionsExtensions.cs (Configuration options)
├── DomainModelExtensions.cs (Domain layer registration)
├── PersistenceModelExtensions.cs (Persistence registration)
├── WebApiExtensions.cs (REST API configuration)
├── GrpcExtensions.cs (gRPC configuration)
├── SignalRExtensions.cs (SignalR configuration)
├── SwaggerExtensions.cs (API documentation)
├── HealthChecksExtensions.cs (Health checks)
├── MassTransitExtensions.cs (Messaging)
├── NServiceBusExtensions.cs (Messaging)
├── OrleansExtensions.cs (Actor model)
├── NHibernateExtensions.cs (ORM)
├── MongoDbExtensions.cs (NoSQL)
├── HangFireExtensions.cs (Background jobs)
├── OpenTelemetryExtensions.cs (Observability)
├── AzureApplicationInsightsExtensions.cs (Monitoring)
├── StartupWarmupGate.cs (Startup coordination)
├── HostedServicesExtensions.cs (Background services)
└── ... (other feature extensions)
Core Components¶
1. Registration Extensions¶
The main orchestration class that coordinates all service registrations and middleware configuration.
ConfigureServices¶
Purpose: Register all services in the dependency injection container.
Usage:
// Startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.ConfigureApplicationServices(this.Configuration, this.WebHostEnvironment);
}
What it registers:
- Configuration options (Options Pattern)
- Hosted services (background workers, warmup gate)
- Domain model services (use cases, domain services)
- Persistence model (repositories, unit of work)
- Service models (REST, gRPC, GraphQL, SignalR)
- Messaging (MassTransit, NServiceBus)
- Actor model (Orleans, Dapr, Akka)
- Background jobs (Hangfire, Quartz)
- Observability (OpenTelemetry, Application Insights)
- Health checks
- Feature flags
- Caching
- Rate limiting
- And more...
Key Features:
- Conditional compilation directives (
#if) for optional features - Order-dependent registration (options first, then services using options)
- Validation and error handling
- Centralized service lifetime management
UseApplicationServices¶
Purpose: Configure the HTTP request pipeline and middleware.
Usage:
// Startup.cs
public void Configure(IApplicationBuilder app, ...)
{
app.UseApplicationServices(this.Configuration, environment, loggerFactory, hostApplicationLifetime);
}
Middleware Pipeline Order:
- Problem Details (error handling)
- Forwarded Headers (reverse proxy)
- Logging (Log4Net, NHibernate, NServiceBus)
- Exception Handling (Developer/Production)
- HSTS (HTTPS enforcement)
- Azure App Configuration (dynamic config)
- Conversation ID (correlation)
- HTTP Logging
- Latency Telemetry
- Static Files
- WebSockets
- Service Models (REST, gRPC, CoreWCF)
- CORS
- HTTPS Redirection
- Routing
- Rate Limiting
- Localization
- Header Propagation
- Swagger (API documentation)
- Hangfire (background jobs)
- OpenTelemetry (observability)
- Orleans Dashboard
- Endpoints (controllers, health checks, etc.)
Key Features:
- Proper middleware ordering for correct behavior
- Environment-specific configuration
- Graceful shutdown handling
- Startup warmup coordination
2. OptionsExtensions¶
Purpose: Centralized configuration options registration and access.
Key Features:
- Validates all options at startup
- Provides static access to options for extension methods
- Fail-fast validation prevents misconfigured deployments
Example:
// OptionsExtensions.cs
internal static class OptionsExtensions
{
internal static ApplicationOptions ApplicationOptions { get; private set; }
internal static ValidationOptions ValidationOptions { get; private set; }
// ... other options
}
// Usage in extension methods
services.AddSingleton(new StartupWarmupGate(
TimeSpan.FromSeconds(OptionsExtensions.ApplicationOptions.StartupWarmupSeconds)));
Registered Options:
ApplicationOptions- Core application configurationValidationOptions- Validation behaviorMicroserviceLocalizationOptions- Localization settingsServiceDiscoveryOptions- Service discovery configurationHealthChecksOptions- Health check configurationSwaggerOptions- API documentation settingsRateLimitingOptions- Rate limiting configurationMassTransitOptions- Messaging configurationNServiceBusOptions- Messaging configurationOrleansOptions- Actor model configuration- And more...
3. DomainModelExtensions¶
Purpose: Register domain layer services (use cases, domain services).
Implementation:
internal static class DomainModelExtensions
{
internal static IServiceCollection AddDomainModelServices(this IServiceCollection services)
{
// Register TimeProvider (time abstraction)
services.AddSingleton<TimeProvider>(sp => TimeProvider.System);
services.ActivateSingleton<TimeProvider>();
// Register domain services (IDomainService)
services.Scan(scan => scan
.FromAssemblyOf<DefaultAggregateRootsRetriever>()
.AddClasses(classes => classes.AssignableTo(typeof(IDomainService)))
.AsImplementedInterfaces()
.WithScopedLifetime());
// Register use cases (IUseCase<,>)
services.Scan(scan => scan
.FromAssemblyOf<DefaultAggregateRootsRetriever>()
.AddClasses(classes => classes.AssignableTo(typeof(IUseCase<,>)))
.AsImplementedInterfaces()
.WithScopedLifetime());
return services;
}
}
Key Features:
- Uses Scrutor for convention-based registration
- Registers all domain services implementing
IDomainService - Registers all use cases implementing
IUseCase<TInput, TOutput> - Scoped lifetime for all domain services
4. PersistenceModelExtensions¶
Purpose: Register persistence layer services (repositories, unit of work).
Implementation:
internal static class PersistenceModelExtensions
{
public static IServiceCollection AddPersistenceModel(this IServiceCollection services)
{
// If multiple persistence models (NHibernate + MongoDB)
// Use NHibernate as primary by default
if (countPersistenceModels > 1)
{
services.AddScoped<IAggregateRootsRepository>(
provider => provider.GetRequiredKeyedService<IAggregateRootsRepository>(
ApplicationConstants.NHibernateDIKey));
// ... other repository interfaces
}
return services;
}
}
Key Features:
- Supports multiple persistence models (NHibernate + MongoDB)
- Uses keyed services for multiple implementations
- Provides primary repository interface when multiple exist
5. StartupWarmupGate¶
Purpose: Coordinates startup readiness and provides grace period for initialization.
Features:
- Grace Period: Configurable startup grace period (default: 20 seconds)
- Manual Readiness: Can be marked ready manually
- Holds: Supports holding readiness until subsystems are ready
- Health Check Integration: Used by health checks to delay readiness
Usage:
// Register in DI
services.AddSingleton(new StartupWarmupGate(TimeSpan.FromSeconds(20)));
services.AddHostedService(sp => sp.GetRequiredService<StartupWarmupGate>());
// Use in health checks
public class MyHealthCheck : IHealthCheck
{
private readonly StartupWarmupGate warmupGate;
public async Task<HealthCheckResult> CheckHealthAsync(...)
{
if (!warmupGate.IsReady)
{
return HealthCheckResult.Degraded("Application is still warming up");
}
// ... check health
}
}
// Mark ready after startup
hostApplicationLifetime.ApplicationStarted.Register(() =>
{
var gate = application.ApplicationServices.GetRequiredService<StartupWarmupGate>();
gate.MarkReady();
});
API:
IsReady: Returns true when ready to serve trafficMarkReady(): Manually mark as readyAcquireHold(): Prevent readinessReleaseHold(): Allow readinessExtendGrace(TimeSpan): Extend grace period
DI Registration Patterns¶
Pattern 1: Feature-Based Extension Methods¶
Each feature has its own extension method for registration:
// MassTransitExtensions.cs
internal static class MassTransitExtensions
{
internal static IServiceCollection AddApplicationMassTransit(
this IServiceCollection services)
{
services.AddMassTransit(x =>
{
// Configure MassTransit
});
return services;
}
}
// Usage in Registration Extensions
#if UseMassTransit
services.AddApplicationMassTransit();
#endif
Benefits:
- Clear feature boundaries
- Easy to enable/disable features
- Self-contained feature registration
- Testable in isolation
Pattern 2: Options-First Registration¶
Options are registered first, then services using those options:
// RegistrationExtensions.cs
public static IServiceCollection ConfigureApplicationServices(...)
{
// 1. Register options FIRST
services.AddApplicationOptions(configuration);
// 2. Register services that use options
services.AddApplicationHostedServices(); // Uses OptionsExtensions.ApplicationOptions
services.AddDomainModelServices();
// ... other services
}
Why:
- Options must be available before services that depend on them
- Static access to options via
OptionsExtensionsproperties - Fail-fast validation prevents misconfigured deployments
Pattern 3: Convention-Based Registration¶
Use Scrutor to automatically register services by convention:
// DomainModelExtensions.cs
services.Scan(scan => scan
.FromAssemblyOf<DefaultAggregateRootsRetriever>()
.AddClasses(classes => classes.AssignableTo(typeof(IDomainService)))
.AsImplementedInterfaces()
.WithScopedLifetime());
Benefits:
- Reduces boilerplate registration code
- Automatic discovery of implementations
- Consistent lifetime management
- Easy to add new services without registration changes
Pattern 4: Keyed Services for Multiple Implementations¶
When multiple implementations exist, use keyed services:
// PersistenceModelExtensions.cs
if (countPersistenceModels > 1)
{
// Use keyed services for multiple implementations
services.AddScoped<IMicroserviceAggregateRootsRepository>(
provider => provider.GetRequiredKeyedService<IMicroserviceAggregateRootsRepository>(
MicroserviceConstants.NHibernateDIKey));
}
Use Cases:
- Multiple persistence providers (NHibernate + MongoDB)
- Multiple messaging frameworks (MassTransit + NServiceBus)
- Feature-specific implementations
Pattern 5: Conditional Registration¶
Use conditional compilation for optional features:
#if UseMassTransit
services.AddApplicationMassTransit();
#endif
#if UseNServiceBus
services.AddNServiceBus(configuration);
#endif
#if UseNHibernate
services.AddNHibernatePersistenceModel(configuration);
#endif
#if UseMongoDb
services.AddMongoDbPersistence(configuration);
#endif
Benefits:
- Only includes features that are enabled
- Reduces binary size
- Compile-time feature selection
- Clear feature dependencies
Middleware Pipeline Configuration¶
Middleware Order Matters¶
The order of middleware in the pipeline is critical:
public static IApplicationBuilder UseApplicationServices(...)
{
// 1. Error handling FIRST (catches all exceptions)
application.UseApplicationProblemDetails();
// 2. Infrastructure (reverse proxy, logging)
application.UseForwardedHeaders();
application.UseApplicationHttpLogging(configuration);
// 3. Security (HTTPS, CORS)
application.UseHttpsRedirection();
application.UseCors();
// 4. Routing (must be before endpoint-specific middleware)
application.UseRouting();
// 5. Feature-specific middleware
application.UseApplicationRateLimiter();
application.UseApplicationLocalization();
application.UseApplicationHeaderPropagation();
// 6. Endpoints (controllers, health checks, etc.)
application.UseEndpoints(endpoints =>
{
endpoints.MapEndpoints();
});
return application;
}
Standard Middleware Pipeline¶
1. ProblemDetails (Error Responses)
2. ForwardedHeaders (Reverse Proxy)
3. Logging (Request/Response Logging)
4. Exception Handling (Developer/Production Pages)
5. HSTS (HTTPS Enforcement)
6. Azure App Configuration (Dynamic Config Refresh)
7. Conversation ID (Correlation)
8. HTTP Logging (Request/Response Logging)
9. Latency Telemetry (Performance Monitoring)
10. Static Files (wwwroot)
11. WebSockets (Real-time Communication)
12. Service Models (REST/gRPC/CoreWCF)
13. CORS (Cross-Origin Resource Sharing)
14. HTTPS Redirection (Security)
15. Routing (Endpoint Routing)
16. Rate Limiting (Throttling)
17. Localization (Multi-language)
18. Header Propagation (Distributed Tracing)
19. Swagger (API Documentation)
20. Hangfire (Background Jobs Dashboard)
21. OpenTelemetry (Observability)
22. Orleans Dashboard (Actor Model)
23. Endpoints (Controllers, Health Checks, etc.)
Environment-Specific Middleware¶
if (environment.IsDevelopment())
{
application.UseDeveloperExceptionPage();
}
else
{
application.UseHsts();
}
Development:
- Developer exception page (detailed errors)
- Enhanced diagnostics
Production:
- HSTS (HTTP Strict Transport Security)
- Generic error pages
- Security headers
Extension Methods Organization¶
Service Registration Extensions (Add*)¶
All service registration methods follow the pattern: AddApplication{Feature} or Add{Feature}
// Examples
services.AddApplicationOptions(configuration);
services.AddApplicationHostedServices();
services.AddDomainModelServices();
services.AddPersistenceModel();
services.AddApplicationMassTransit();
services.AddApplicationHealthChecks(configuration);
services.AddApplicationRateLimiting();
services.AddApplicationOpenTelemetry(environment);
Naming Convention:
AddApplication{Feature}- Application-specific featuresAdd{Feature}- Generic features (if not application-specific)
Middleware Extensions (Use*)¶
All middleware configuration methods follow the pattern: UseApplication{Feature} or standard ASP.NET Core names
// Examples
application.UseApplicationProblemDetails();
application.UseApplicationHttpLogging(configuration);
application.UseApplicationRateLimiter();
application.UseApplicationLocalization();
application.UseApplicationHeaderPropagation();
application.UseApplicationSwagger();
application.UseApplicationOpenTelemetry();
Naming Convention:
UseApplication{Feature}- Application-specific middleware- Standard ASP.NET Core middleware uses standard names (e.g.,
UseRouting,UseCors)
Endpoint Mapping Extensions (Map*)¶
All endpoint mapping methods follow the pattern: MapApplication{Feature} or standard ASP.NET Core names
// Examples
endpoints.MapApplicationControllers();
endpoints.MapApplicationHealthChecks();
endpoints.MapApplicationSwagger();
endpoints.MapGrpcServices();
endpoints.MapSignalR();
Naming Convention:
MapApplication{Feature}- Application-specific endpoints- Standard ASP.NET Core endpoints use standard names (e.g.,
MapControllers,MapGrpcServices)
Cross-Cutting Concerns Integration¶
1. Logging¶
Registration:
#if Serilog
services.AddAndConfigureSerilogLogging(configuration, environment);
#endif
#if Log4Net
loggerFactory.UseLog4Net();
#endif
#if OpenTelemetry
logging.AddOpenTelemetry(options =>
{
options.IncludeScopes = true;
options.ParseStateValues = true;
});
#endif
Middleware:
#if Serilog
application.UseApplicationSerilogRequestLogging();
#endif
application.UseApplicationHttpLogging(configuration);
2. Observability¶
OpenTelemetry:
#if OpenTelemetry
services.AddApplicationOpenTelemetry(environment);
// ... in middleware pipeline
application.UseApplicationOpenTelemetry();
#endif
Application Insights:
Metrics:
3. Health Checks¶
Registration:
Endpoints:
4. Caching¶
Redis:
In-Memory:
5. Rate Limiting¶
Registration:
Middleware:
6. Feature Flags¶
Registration:
7. Localization¶
Registration:
Middleware:
8. Header Propagation¶
Registration:
Middleware:
Service Model Integration¶
REST API¶
Registration:
Middleware:
#if UseRestApi
application.UseApplicationProblemDetails();
application.UseWebApiCommunication();
#endif
Endpoints:
gRPC¶
Registration:
Endpoints:
SignalR¶
Registration:
Endpoints:
GraphQL¶
Registration:
Endpoints:
CoreWCF¶
Registration:
Middleware:
Messaging Integration¶
MassTransit¶
Registration:
Features:
- Consumers registration
- Saga state machines
- Outbox pattern
- Retry policies
NServiceBus¶
Registration:
Startup:
Shutdown:
Persistence Integration¶
NHibernate¶
Registration:
#if UseNHibernate
services.AddNHibernatePersistenceModel(configuration);
#endif
#if (UseNHibernate && Migrations)
services.AddApplicationFluentMigrator(configuration, typeof(MicroserviceMigration).Assembly);
#endif
Middleware:
#if UseNHibernate
loggerFactory.UseNHibernateLogging();
application.RunMicroserviceFluentMigrations();
#endif
MongoDB¶
Registration:
#if UseMongoDb
#if Migrations
services.AddMongoDbMigrator(configuration, typeof(MicroserviceMongoDbMigration));
#endif
services.AddMongoDbPersistence(configuration);
#endif
Background Jobs Integration¶
Hangfire¶
Registration:
Middleware:
Actor Model Integration¶
Orleans¶
Host Builder:
Endpoints:
AI Integration¶
Semantic Kernel¶
Registration:
Logging:
Microsoft Bot Framework¶
Registration:
Model Context Protocol (MCP)¶
Registration:
Endpoints:
Startup Warmup Coordination¶
StartupWarmupGate¶
Purpose: Provides grace period and coordination for startup readiness.
Registration:
services.AddApplicationHostedServices();
// Internally registers:
// services.AddSingleton(new StartupWarmupGate(TimeSpan.FromSeconds(20)));
// services.AddHostedService(sp => sp.GetRequiredService<StartupWarmupGate>());
Usage in Health Checks:
public class MyHealthCheck : IHealthCheck
{
private readonly StartupWarmupGate warmupGate;
public async Task<HealthCheckResult> CheckHealthAsync(...)
{
if (!warmupGate.IsReady)
{
return HealthCheckResult.Degraded("Application is still warming up");
}
// ... perform health checks
}
}
Mark Ready After Startup:
hostApplicationLifetime.ApplicationStarted.Register(() =>
{
var gate = application.ApplicationServices.GetRequiredService<StartupWarmupGate>();
gate.MarkReady();
});
Use Cases:
- Database migrations completion
- Messaging bus connection
- Cache warmup
- External service health verification
- Long-running initialization tasks
Best Practices¶
Do's¶
-
Register Options First
-
Use Feature-Based Extension Methods
-
Follow Naming Conventions
-
Use Conditional Compilation for Optional Features
-
Validate Input Parameters
-
Return ServiceCollection for Chaining
Don'ts¶
-
Don't Register Services Before Options
-
Don't Mix Business Logic with Registration
-
Don't Access Services During Registration
-
Don't Skip Validation
-
Don't Use Wrong Service Lifetime
Common Patterns¶
Pattern 1: Conditional Feature Registration¶
public static IServiceCollection ConfigureMicroserviceServices(...)
{
// Always register
services.AddApplicationOptions(configuration);
services.AddDomainModelServices();
// Conditionally register
#if UseMassTransit
services.AddApplicationMassTransit();
#endif
#if UseNServiceBus
services.AddNServiceBus(configuration);
#endif
return services;
}
Pattern 2: Options-Based Configuration¶
internal static class MyFeatureExtensions
{
internal static IServiceCollection AddMicroserviceMyFeature(
this IServiceCollection services,
IConfiguration configuration)
{
// Register options
services.AddOptions<MyFeatureOptions>()
.Bind(configuration.GetSection("MyFeature"))
.ValidateDataAnnotations()
.ValidateOnStart();
// Register services using options
services.AddScoped<IMyFeatureService, MyFeatureService>();
return services;
}
}
Pattern 3: Middleware Pipeline Configuration¶
internal static class MyFeatureExtensions
{
internal static IApplicationBuilder UseMicroserviceMyFeature(
this IApplicationBuilder application)
{
// Configure middleware
application.UseMiddleware<MyFeatureMiddleware>();
return application;
}
}
Pattern 4: Endpoint Mapping¶
internal static class MyFeatureExtensions
{
internal static IEndpointRouteBuilder MapMicroserviceMyFeature(
this IEndpointRouteBuilder endpoints)
{
endpoints.MapGet("/myfeature", async context =>
{
await context.Response.WriteAsync("My Feature");
});
return endpoints;
}
}
Pattern 5: Hosted Service Registration¶
internal static class HostedServicesExtensions
{
internal static IServiceCollection AddMicroserviceHostedServices(
this IServiceCollection services)
{
services.AddSingleton<StartupWarmupGate>();
services.AddHostedService(sp => sp.GetRequiredService<StartupWarmupGate>());
return services;
}
}
Testing¶
Unit Testing Extension Methods¶
[TestMethod]
public void AddMicroserviceFeature_ShouldRegisterServices()
{
// Arrange
var services = new ServiceCollection();
var configuration = new ConfigurationBuilder()
.AddInMemoryCollection(new Dictionary<string, string>
{
["MyFeature:Enabled"] = "true"
})
.Build();
// Act
services.AddApplicationFeature(configuration);
var provider = services.BuildServiceProvider();
// Assert
var service = provider.GetService<IMyFeatureService>();
Assert.IsNotNull(service);
}
Integration Testing¶
[TestMethod]
public async Task ConfigureMicroserviceServices_ShouldRegisterAllServices()
{
// Arrange
var services = new ServiceCollection();
var configuration = new ConfigurationBuilder()
.AddJsonFile("appsettings.json")
.Build();
var environment = new WebHostEnvironment { EnvironmentName = "Development" };
// Act
services.ConfigureMicroserviceServices(configuration, environment);
var provider = services.BuildServiceProvider();
// Assert
var domainService = provider.GetService<IDomainService>();
Assert.IsNotNull(domainService);
var repository = provider.GetService<IRepository>();
Assert.IsNotNull(repository);
}
Troubleshooting¶
Issue: Service Not Registered¶
Symptoms: InvalidOperationException: Unable to resolve service for type 'X'.
Solutions:
- Verify extension method is called in
ConfigureMicroserviceServices - Check conditional compilation directives (
#if) - Verify service lifetime (Singleton vs Scoped vs Transient)
- Check for missing options registration
Issue: Options Not Available¶
Symptoms: Options are null or default values.
Solutions:
- Ensure
AddMicroserviceOptions(configuration)is called first - Verify configuration section exists in
appsettings.json - Check options validation doesn't fail
- Verify options are accessed after service provider is built
Issue: Middleware Order Issues¶
Symptoms: Middleware doesn't execute or executes in wrong order.
Solutions:
- Verify middleware order in
UseMicroserviceServices - Check middleware is placed before
UseRoutingif needed - Ensure endpoint-specific middleware is after
UseRouting - Review middleware dependencies
Issue: Startup Warmup Not Working¶
Symptoms: Health checks fail immediately after startup.
Solutions:
- Verify
StartupWarmupGateis registered - Check
StartupWarmupSecondsconfiguration - Ensure
MarkReady()is called after startup - Verify health checks check
IsReadyproperty
References¶
- Configuration: configuration.md
- Options Pattern: configuration.md
- Health Checks: health-checks.md
- Startup and Warmup: startup-warmup.md: Startup warmup patterns and mechanisms
Summary¶
The Application Model in ConnectSoft Templates provides:
- ✅ Centralized Orchestration: Single entry point for all service registrations
- ✅ Clean Separation: Application project stays minimal, ApplicationModel handles complexity
- ✅ Consistent Patterns: Standardized extension method naming and organization
- ✅ Feature-Based: Optional features via conditional compilation
- ✅ Middleware Pipeline: Properly ordered middleware configuration
- ✅ Cross-Cutting Concerns: Integrated logging, observability, health checks, etc.
- ✅ Startup Coordination: Warmup gate for graceful startup
- ✅ Testability: Easy to test and override individual components
By following these patterns, teams can:
- Maintain Clean Architecture: Keep Application project minimal
- Enable Features Selectively: Use conditional compilation for optional features
- Ensure Proper Configuration: Options-first registration prevents misconfiguration
- Build Production-Ready Services: Comprehensive cross-cutting concerns integration
- Test Effectively: Clear separation enables easy testing
- Scale Consistently: Standardized patterns across all ConnectSoft templates
The Application Model is the glue that binds all layers and features together, ensuring the application is properly configured, observable, and production-ready.