Skip to content

Service Configuration ​

Comprehensive service configuration and setup patterns for Momentum applications, including service defaults, .NET Aspire orchestration, observability, and multi-tenant deployment configuration.

Overview ​

Momentum provides opinionated service configuration that follows .NET best practices while adding specialized functionality for microservices:

  • Service Defaults: Pre-configured common services and middleware with automatic discovery
  • Aspire Integration: .NET Aspire orchestration for local development and service discovery
  • API Setup: REST and gRPC endpoint configuration with OpenAPI documentation
  • Observability: OpenTelemetry integration with structured logging, metrics, and distributed tracing
  • Port Allocation: Systematic port management across development and production environments
  • Multi-Tenant Configuration: Tenant-aware configuration patterns for SaaS applications
  • Environment Management: Development, staging, and production configuration strategies

Core Components ​

Service Defaults ​

Service Defaults provide pre-configured, production-ready service registration including Wolverine CQRS, FluentValidation, OpenTelemetry, and health checks with automatic assembly discovery.

API Setup ​

API Setup configures REST and gRPC endpoints with OpenAPI documentation, automatic validation, exception handling, and security middleware.

Observability ​

Observability implements comprehensive monitoring through OpenTelemetry integration, structured logging with Serilog, custom metrics, and distributed tracing.

Port Allocation ​

Port Allocation provides systematic port assignment patterns for .NET Aspire, Docker Compose, and production deployment environments.

Configuration Patterns ​

.NET Aspire Orchestration ​

Momentum templates include .NET Aspire orchestration for seamless local development:

csharp
// AppHost project - orchestrates all services
var builder = DistributedApplication.CreateBuilder(args);

// PostgreSQL database with automatic connection string management
var database = builder.AddPostgres("postgres")
    .WithPgAdmin()
    .AddDatabase("AppDomainDb");

// Kafka message broker
var kafka = builder.AddKafka("kafka")
    .WithKafkaUI();

// API service with service discovery
var api = builder.AddProject<Projects.AppDomain_Api>("api")
    .WithReference(database)
    .WithReference(kafka)
    .WithHttpEndpoint(port: 8101, name: "http")
    .WithHttpsEndpoint(port: 8111, name: "https");

// BackOffice service for background processing
var backOffice = builder.AddProject<Projects.AppDomain_BackOffice>("backoffice")
    .WithReference(database)
    .WithReference(kafka);

builder.Build().Run();

Layered Configuration Hierarchy ​

Configuration follows the standard .NET configuration hierarchy with Momentum enhancements:

  1. appsettings.json: Base application configuration
  2. appsettings.{Environment}.json: Environment-specific overrides
  3. Environment Variables: Container and cloud-friendly runtime configuration
  4. User Secrets: Development-time secure storage
  5. Cloud Key Vault/Secret Manager: Production secrets management
  6. Aspire Service Discovery: Automatic service endpoint resolution

Service Registration with Domain Assembly Discovery ​

csharp
using Momentum.ServiceDefaults;
using AppDomain.Domain;

// Mark domain assemblies for automatic discovery
[assembly: DomainAssembly(typeof(IAppDomainAssembly))]

var builder = WebApplication.CreateBuilder(args);

// Core Momentum service defaults with automatic discovery
builder.AddServiceDefaults();

// API-specific configuration (if building an API service)
builder.AddApiServiceDefaults();

// Custom application services
builder.Services.AddScoped<IEmailService, EmailService>();
builder.Services.AddDbContext<AppDomainDb>(options =>
    options.UseNpgsql(builder.Configuration.GetConnectionString("AppDomainDb")));

var app = builder.Build();

// Configure middleware pipeline
app.ConfigureApiUsingDefaults(requireAuth: true);

// Map endpoints
app.MapControllers();
app.MapDefaultHealthCheckEndpoints();

await app.RunAsync(args);

Configuration Validation ​

Implement configuration validation at startup:

csharp
// Configuration model with validation
public class AppConfiguration
{
    [Required]
    public string ApplicationName { get; set; } = string.Empty;

    [Required]
    [Url]
    public string BaseUrl { get; set; } = string.Empty;

    [Range(1, 3600)]
    public int TokenExpirationMinutes { get; set; } = 60;
}

// Register and validate configuration
builder.Services.Configure<AppConfiguration>(
    builder.Configuration.GetSection("App"));

builder.Services.AddOptions<AppConfiguration>()
    .BindConfiguration("App")
    .ValidateDataAnnotations()
    .ValidateOnStart();

Key Features ​

Automatic Service Discovery ​

Momentum automatically discovers and registers services from marked assemblies:

  • CQRS Handlers: Command and query handlers with Wolverine
  • Validators: FluentValidation validators for all commands and queries
  • Integration Events: Event handlers for cross-service communication
  • Database Commands: Source-generated database command handlers

Multi-Tenant Configuration ​

Built-in support for multi-tenant scenarios:

csharp
// Tenant-aware configuration
public class TenantConfiguration
{
    public string TenantId { get; set; } = string.Empty;
    public string DatabaseConnectionString { get; set; } = string.Empty;
    public Dictionary<string, string> Features { get; set; } = new();
}

// Register tenant configuration provider
builder.Services.AddSingleton<ITenantConfigurationProvider, TenantConfigurationProvider>();

Production-Ready Defaults ​

  • OpenTelemetry Integration: Distributed tracing, metrics, and logging
  • Health Checks: Comprehensive application and infrastructure monitoring
  • Middleware Pipeline: Pre-configured exception handling, CORS, and security
  • Configuration Validation: Startup validation with clear error messages
  • Secrets Management: Azure Key Vault and user secrets integration
  • Hot Reload: Development-time configuration and code updates

Service Categories ​

Infrastructure Services ​

  • Database connections
  • Message broker configuration
  • Caching setup
  • External API clients

Application Services ​

  • CQRS handlers
  • Domain services
  • Integration services
  • Background services

Cross-Cutting Services ​

  • Logging and telemetry
  • Authentication and authorization
  • Validation and error handling
  • Request/response middleware

Environment Management ​

Development with .NET Aspire ​

Local development is orchestrated through .NET Aspire for optimal developer experience:

bash
# Start the complete application stack
dotnet run --project src/AppDomain.AppHost

# Access Aspire Dashboard at https://localhost:18110

Development Features:

  • Automatic Service Discovery: Services find each other automatically
  • Infrastructure Management: PostgreSQL and Kafka containers managed by Aspire
  • Hot Reload: Code and configuration changes applied instantly
  • Structured Logging: All service logs aggregated in Aspire dashboard
  • Health Monitoring: Real-time health status of all services

Staging Environment ​

Production-like environment for validation and performance testing:

json
// appsettings.Staging.json
{
  "ConnectionStrings": {
    "AppDomainDb": "Host=staging-db;Database=appdomain;Username=appuser"
  },
  "Kafka": {
    "BootstrapServers": "staging-kafka:9092"
  },
  "OpenTelemetry": {
    "Endpoint": "https://staging-otel.company.com"
  },
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft": "Warning"
    }
  }
}

Production Deployment ​

Optimized for performance, security, and reliability:

json
// appsettings.Production.json
{
  "ConnectionStrings": {
    "AppDomainDb": "$(DB_CONNECTION_STRING)" // From Key Vault
  },
  "Kafka": {
    "BootstrapServers": "$(KAFKA_BOOTSTRAP_SERVERS)"
  },
  "OpenTelemetry": {
    "Endpoint": "$(OTEL_EXPORTER_OTLP_ENDPOINT)"
  },
  "Logging": {
    "LogLevel": {
      "Default": "Warning",
      "AppDomain": "Information"
    }
  },
  "HealthChecks": {
    "Enabled": true,
    "TimeoutSeconds": 30
  }
}

Production Considerations:

  • Security Hardening: Secrets stored in Azure Key Vault
  • Performance Optimization: Connection pooling and caching enabled
  • Monitoring: Comprehensive telemetry and alerting
  • High Availability: Load balancing and failover configuration
  • Disaster Recovery: Backup and restore procedures

Configuration Sources ​

  1. Default Settings: Built-in sensible defaults
  2. Configuration Files: JSON, XML, INI file support
  3. Environment Variables: Container and cloud-friendly
  4. Command Line: Runtime parameter override
  5. External Providers: Azure Key Vault, AWS Parameter Store

Best Practices ​

  • Use strongly-typed configuration classes
  • Validate configuration at startup
  • Separate environment-specific settings
  • Use secrets management for sensitive data
  • Document configuration options
  • Provide sensible defaults

Common Configuration Scenarios ​

API Service Configuration ​

Complete setup for a REST/gRPC API service:

csharp
using Momentum.ServiceDefaults;
using Momentum.ServiceDefaults.Api;
using AppDomain.Domain;

[assembly: DomainAssembly(typeof(IAppDomainAssembly))]

var builder = WebApplication.CreateSlimBuilder(args);

// Core Momentum configuration
builder.AddServiceDefaults();
builder.AddApiServiceDefaults();

// Database context with connection string from Aspire
builder.Services.AddDbContext<AppDomainDb>(options =>
    options.UseNpgsql(builder.Configuration.GetConnectionString("AppDomainDb")));

// Authentication
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
    .AddJwtBearer(options =>
    {
        options.Authority = builder.Configuration["Auth:Authority"];
        options.Audience = builder.Configuration["Auth:Audience"];
    });

var app = builder.Build();

// Configure middleware pipeline with authentication
app.ConfigureApiUsingDefaults(requireAuth: true);

// Map endpoints
app.MapControllers();
app.MapDefaultHealthCheckEndpoints();

await app.RunAsync(args);

Background Service Configuration ​

Setup for asynchronous message processing services:

csharp
using Momentum.ServiceDefaults;
using AppDomain.Domain;

[assembly: DomainAssembly(typeof(IAppDomainAssembly))]

var builder = Host.CreateApplicationBuilder(args);

// Service defaults work with generic hosts
builder.Services.AddServiceDefaults(builder);

// Database and messaging
builder.Services.AddDbContext<AppDomainDb>(options =>
    options.UseNpgsql(builder.Configuration.GetConnectionString("AppDomainDb")));

// Background services
builder.Services.AddHostedService<EventProcessingService>();

var app = builder.Build();

await app.RunAsync();

Orleans Stateful Service Configuration ​

Setup for Orleans-based stateful processing:

csharp
using Momentum.ServiceDefaults;
using Microsoft.Extensions.Hosting;
using Orleans.Hosting;

var builder = Host.CreateApplicationBuilder(args);

builder.Services.AddServiceDefaults(builder);

// Orleans configuration
builder.UseOrleans(silo =>
{
    silo.UseLocalhostClustering()
        .ConfigureLogging(logging => logging.AddConsole())
        .UseDashboard(options => { });
});

// Register grain services
builder.Services.AddScoped<IGrainService, GrainService>();

var app = builder.Build();

await app.RunAsync();

Multi-Tenant Service Configuration ​

Configuration for SaaS applications with tenant isolation:

csharp
using Momentum.ServiceDefaults;
using Momentum.ServiceDefaults.Api;

var builder = WebApplication.CreateSlimBuilder(args);

builder.AddServiceDefaults();
builder.AddApiServiceDefaults();

// Multi-tenant database context
builder.Services.AddDbContext<AppDomainDb>((serviceProvider, options) =>
{
    var tenantProvider = serviceProvider.GetRequiredService<ITenantProvider>();
    var connectionString = tenantProvider.GetConnectionString();
    options.UseNpgsql(connectionString);
});

// Tenant resolution middleware
builder.Services.AddScoped<ITenantProvider, TenantProvider>();
builder.Services.AddScoped<TenantResolutionMiddleware>();

var app = builder.Build();

// Add tenant resolution before other middleware
app.UseMiddleware<TenantResolutionMiddleware>();
app.ConfigureApiUsingDefaults();

await app.RunAsync(args);

Getting Started ​

1. Install the Momentum Template ​

bash
# Install the template (if working from source)
dotnet new install .

# Create a new project
dotnet new mmt -n MyBusinessApp --aspire --api --backoffice

2. Configure Service Defaults ​

Mark your domain assemblies and add service defaults:

csharp
// In your domain assembly
namespace MyBusinessApp.Domain;
public interface IMyBusinessAppDomainAssembly;

// In Program.cs
using MyBusinessApp.Domain;
[assembly: DomainAssembly(typeof(IMyBusinessAppDomainAssembly))]

var builder = WebApplication.CreateBuilder(args);
builder.AddServiceDefaults();

3. Start with Aspire Orchestration ​

bash
# Start the complete application stack
dotnet run --project src/MyBusinessApp.AppHost

# Open Aspire Dashboard: https://localhost:18110

4. Add Environment-Specific Configuration ​

Create configuration files for each environment:

  • appsettings.Local.json — all local development overrides (logging, connection strings, feature flags), excluded from Docker via .dockerignore
  • appsettings.Staging.json
  • appsettings.Production.json

5. Configure Production Deployment ​

Set up Azure Key Vault or equivalent secrets management:

csharp
if (builder.Environment.IsProduction())
{
    builder.Configuration.AddAzureKeyVault(
        vaultUri: new Uri(builder.Configuration["KeyVault:VaultUri"]!),
        credential: new DefaultAzureCredential());
}

6. Monitor and Observe ​

Momentum includes comprehensive observability out-of-the-box:

  • Structured Logging: Serilog with OpenTelemetry integration
  • Distributed Tracing: Automatic tracing across services
  • Health Checks: Application and infrastructure monitoring
  • Metrics: Business and technical metrics collection

Troubleshooting Common Issues ​

Service Discovery Problems ​

Issue: Services can't find each other in Aspire environment

Solution:

csharp
// Ensure service names match between AppHost and service configuration
var api = builder.AddProject<Projects.AppDomain_Api>("api"); // Name must match

// In consuming service, use the exact name:
var httpClient = serviceProvider.GetRequiredService<HttpClient>();
var response = await httpClient.GetAsync("https://api/health");

Configuration Validation Errors ​

Issue: Application fails to start due to configuration validation

Solution:

csharp
// Add detailed configuration validation
builder.Services.AddOptions<MyConfiguration>()
    .BindConfiguration("MySection")
    .ValidateDataAnnotations()
    .Validate(config => !string.IsNullOrEmpty(config.RequiredProperty),
        "RequiredProperty cannot be empty")
    .ValidateOnStart();

Assembly Discovery Issues ​

Issue: Commands, queries, or validators not being discovered

Solution:

csharp
// Ensure all domain assemblies are marked
[assembly: DomainAssembly(typeof(IDomainMarker))]

// Or manually specify assemblies
ServiceDefaultsExtensions.EntryAssembly = typeof(Program).Assembly;

Health Check Failures ​

Issue: Health checks failing during startup

Solution:

csharp
// Add health check timeout and retry policies
builder.Services.AddHealthChecks()
    .AddNpgSql(connectionString, timeout: TimeSpan.FromSeconds(30))
    .AddCheck("startup-delay", () =>
    {
        // Allow time for dependencies to start
        return Task.FromResult(HealthCheckResult.Healthy());
    });

OpenTelemetry Not Working ​

Issue: Telemetry data not appearing in monitoring tools

Solution:

json
// Verify OpenTelemetry configuration
{
  "OpenTelemetry": {
    "Endpoint": "http://localhost:4317",
    "ServiceName": "MyService",
    "ServiceVersion": "1.0.0"
  }
}

Best Practices Summary ​

  1. Use .NET Aspire for Local Development: Leverage Aspire's orchestration capabilities for the best developer experience
  2. Mark Domain Assemblies: Always mark assemblies containing domain logic with [DomainAssembly]
  3. Validate Configuration Early: Use configuration validation to catch issues at startup
  4. Implement Comprehensive Health Checks: Monitor all critical dependencies
  5. Use Structured Logging: Include relevant context in all log messages
  6. Secure Production Secrets: Never store secrets in configuration files
  7. Monitor Everything: Implement observability from day one
  8. Test Configuration: Include configuration testing in your test suites
  • Service Defaults: Detailed service defaults configuration and automatic discovery
  • API Setup: REST and gRPC API configuration patterns
  • Observability: OpenTelemetry integration and monitoring setup
  • Port Allocation: Systematic port management across environments
  • CQRS: Command and query patterns with Wolverine
  • Messaging: Event-driven architecture with Kafka
  • Database: Database setup and migration patterns
  • Testing: Integration and unit testing strategies