Skip to content
bitzorcas
EN

Concept

Dependency injection & composition root

How BitzOrcas.Modern composes the host — four registration stages, the adapter override pattern, Mediator source generation, and the middleware pipeline order.

Last updated

BitzOrcas.Modern’s composition root is src/Hosts/BitzOrcas.Api/Program.cs. It’s deliberately explicit — every registration call is visible, no auto-discovery magic, no convention-based scanning that hides wiring failures. Four registration stages, each with a clear responsibility:

var builder = WebApplication.CreateBuilder(args);
// 1. Cross-host defaults (OTel + Health Checks)
builder.AddServiceDefaults();
// 2. Core runtime (context, clock, audit, tenancy, placeholder adapters)
builder.Services.AddBitzOrcasCoreRuntime();
// 3. Persistence adapters (SqlSugar/EfCore — conditional on config)
builder.Services.AddBitzOrcasPersistenceAdapters();
// 4. Authentication (JWT/HMAC/ApiKey)
builder.Services.AddBitzOrcasAuthentication();
// 5. API pipeline (Mediator + Mapster + JSON + OpenAPI + RateLimiter)
builder.Services.AddBitzOrcasApiPipeline();
var app = builder.Build();
app.MapBitzOrcasEndpoints();
await app.RunAsync();

The four registration stages

1. AddServiceDefaults() — cross-host defaults

Registered in BitzOrcas.ServiceDefaults. This is the only place OTel is registered — individual hosts must not call AddOpenTelemetry() independently. Registers:

  • Traces: ASP.NET Core + HttpClient + custom BitzOrcas ActivitySources
  • Metrics: ASP.NET Core + HttpClient + Runtime
  • Exporter: OTLP (gRPC) to OTEL_EXPORTER_OTLP_ENDPOINT (default localhost:4317)
  • Resource attributes: service.name, service.version, deployment.environment
  • Health Checks: self check with LivenessTag

2. AddBitzOrcasCoreRuntime() — cross-cutting context

Registers the foundational services that every host needs:

  • ISystemClock / FixedClock — deterministic time
  • ICurrentUser / AnonymousCurrentUser — unified caller model (Api uses JWT-populated, JobHost uses anonymous)
  • ITenantContext / ITenantResolver — 8-level tenant resolution chain
  • IUnitOfWork / NullUnitOfWork — placeholder, overridden by persistence adapters
  • 7 audit sinks (all Null*AuditSink defaults) — overridden by production audit
  • IFileStorage / LocalFileStorage — local filesystem default
  • INotificationPublisher / NullNotificationPublisher — placeholder
  • IAppCache / FusionCacheAppCache — caching
  • ICorrelationContext — correlation ID propagation
  • Mapster global configuration scanning

3. AddBitzOrcasPersistenceAdapters() — ORM adapter override

Conditional registration: only activates when both ConnectionStrings:Default AND RabbitMq:Host are configured. When active, it overrides the Null/InMemory defaults with production adapters:

ServiceDefault (CoreRuntime)Production Override
IUnitOfWorkNullUnitOfWorkSqlSugarUnitOfWork or CapSqlSugarUnitOfWork
IAuditLogStoreUnavailableAuditLogStore (fail-loud)SqlSugarAuditLogStore
IFileAssetRepositoryInMemoryFileAssetRepositorySqlSugarFileAssetRepository
ITicketRepositoryInMemoryTicketRepositorySqlSugarTicketRepository
ICatalogRepositoryInMemoryCatalogRepositorySqlSugarCatalogRepository
… (all platform ports)InMemory* / Null*SqlSugar*

4. AddBitzOrcasAuthentication() — multi-scheme auth

Three mutually exclusive authentication schemes:

SchemeHandlerPurpose
JWT BearerMicrosoft.AspNetCore.Authentication.JwtBearerUser authentication
HMACHmacAuthenticationHandlerExternal application auth (nonce anti-replay)
ApiKeyApiKeyAuthenticationHandlerAPI key auth (SHA-256 hashed)

5. AddBitzOrcasApiPipeline() — Mediator + API surface

  • Mediator 3 source-generated dispatch
  • Mapster object mapping
  • JSON serialization options
  • OpenAPI + Scalar UI
  • ProblemDetails (RFC 9457)
  • Rate limiter (3 policies: TokenBucket, SlidingWindow, FixedWindow)

The middleware pipeline

Explicitly ordered in Program.cs, never extracted into a helper:

UseExceptionHandler (ProblemDetails)
→ CorrelationIdMiddleware
→ UseAuthentication
→ DelegationTokenMiddleware
→ TenantResolutionMiddleware
→ RequestAuditMiddleware
→ UseAuthorization
→ UseRateLimiter
→ Endpoints

Ordering rules:

  • CorrelationId before everything. Every log line, trace span, and audit row carries the correlation ID.
  • Authentication before tenant resolution. Tenant resolution needs claims from authenticated users.
  • Tenant resolution before request audit. The audit envelope needs the tenant context.
  • Rate limiter after authorization. Unauthenticated traffic shouldn’t consume rate limit tokens.

Schema initialization modes

The API supports CLI-driven schema initialization — triggered before the web pipeline starts:

FlagBehavior
--init-schemaCreate tables (CodeFirst), create audit sharded tables, seed 14 platform system tables
--seed-demoSame as --init-schema
--seed-onlySeed data only (no schema creation)
--no-seedSchema creation only (no seed data)

When any init flag is present, the host performs schema init and exits without starting the web pipeline. This is how the first-run experience works.

The two host profiles

HostPurposeAuthTenancyTrim
BitzOrcas.ApiHTTP APIJWT + HMAC + ApiKey8-level chainYes (SqlSugar path)
BitzOrcas.JobHostBackground jobsNoneNone (AnonymousCurrentUser)No (Quartz)

The JobHost uses a minimal Core Runtime — clock, anonymous user, audit, storage. No authentication, no tenant resolution, no rate limiting. ICurrentUser resolves to AnonymousCurrentUser with CallerType.System.