Skip to content

Architecture Design

samples/ai-model-governance/
├── AiModelGovernance.slnx # Solution file (8 projects)
├── Directory.Build.props # FunctoriumSrcRoot + common settings
├── Directory.Build.targets # Root inheritance blocking
├── domain/ # Domain layer docs (4)
├── application/ # Application layer docs (4)
├── adapter/ # Adapter layer docs (4)
├── observability/ # Observability docs (4)
├── Src/
│ ├── AiGovernance.Domain/ # Domain Layer
│ │ ├── SharedModels/Services/ # Domain Services (2 types)
│ │ └── AggregateRoots/
│ │ ├── Models/ # AIModel + VOs(4) + Specs(2)
│ │ ├── Deployments/ # ModelDeployment + VOs(4) + Specs(3)
│ │ ├── Assessments/ # ComplianceAssessment + Entity(1) + VOs(3) + Specs(3)
│ │ └── Incidents/ # ModelIncident + VOs(4) + Specs(4)
│ ├── AiGovernance.Application/ # Application Layer
│ │ └── Usecases/
│ │ ├── Models/ # Commands(2), Queries(2), Ports(2)
│ │ ├── Deployments/ # Commands(4), Queries(2), Ports(4)
│ │ ├── Assessments/ # Commands(1), Queries(1), EventHandlers(1)
│ │ └── Incidents/ # Commands(1), Queries(2), Ports(1), EventHandlers(1)
│ ├── AiGovernance.Adapters.Infrastructure/ # Mediator, OpenTelemetry, External Services
│ │ ├── ExternalServices/ # Advanced IO Features (4 types)
│ │ └── Registrations/
│ ├── AiGovernance.Adapters.Persistence/ # InMemory, EfCore Repository/Query
│ │ ├── Models/ # Repository(2) + Query(2)
│ │ ├── Deployments/ # Repository(2) + Query(2)
│ │ ├── Assessments/ # Repository(2)
│ │ ├── Incidents/ # Repository(2) + Query(1)
│ │ └── Registrations/
│ ├── AiGovernance.Adapters.Presentation/ # FastEndpoints
│ │ ├── Endpoints/ # HTTP API (15 types)
│ │ └── Registrations/
│ └── AiGovernance/ # Host (Program.cs)
└── Tests/
├── AiGovernance.Tests.Unit/ # Unit Tests
└── AiGovernance.Tests.Integration/ # Integration Tests

AiModelGovernance.slnx contains 8 projects.

<Solution>
<Project Path="Src/AiGovernance.Domain/AiGovernance.Domain.csproj" />
<Project Path="Src/AiGovernance.Application/AiGovernance.Application.csproj" />
<Project Path="Src/AiGovernance.Adapters.Infrastructure/AiGovernance.Adapters.Infrastructure.csproj" />
<Project Path="Src/AiGovernance.Adapters.Persistence/AiGovernance.Adapters.Persistence.csproj" />
<Project Path="Src/AiGovernance.Adapters.Presentation/AiGovernance.Adapters.Presentation.csproj" />
<Project Path="Src/AiGovernance/AiGovernance.csproj" />
<Project Path="Tests/AiGovernance.Tests.Unit/AiGovernance.Tests.Unit.csproj" />
<Project Path="Tests/AiGovernance.Tests.Integration/AiGovernance.Tests.Integration.csproj" />
</Solution>
ProjectLayerRole
AiGovernance.DomainDomainAggregate, VO, Specification, Domain Service, Domain Event
AiGovernance.ApplicationApplicationCommand/Query Usecase, Port Interfaces, Event Handler
AiGovernance.Adapters.InfrastructureAdapterMediator, OpenTelemetry, Pipeline, External Service
AiGovernance.Adapters.PersistenceAdapterRepository/Query Implementation (InMemory, EfCore)
AiGovernance.Adapters.PresentationAdapterFastEndpoints HTTP API
AiGovernanceHostProgram.cs, DI Assembly, appsettings.json
AiGovernance.Tests.UnitTestUnit Tests (VO, Aggregate, Domain Service, Architecture)
AiGovernance.Tests.IntegrationTestIntegration Tests (HTTP Endpoint E2E)

AiGovernance (Host)
├── Adapters.Infrastructure → Functorium + Functorium.Adapters + Application
├── Adapters.Persistence → Functorium.Adapters + Application + SourceGenerators
├── Adapters.Presentation → Application
└── Application → Domain
Domain → Functorium + SourceGenerators

Core Principle: Dependencies flow only inward. Domain knows nothing about the outside, Application knows only port interfaces, and Adapters provide the implementations.


DimensionExpressionExample
Aggregate (What)Primary FolderModels/, Deployments/, Assessments/, Incidents/
CQRS Role (Read/Write)Secondary FolderRepositories/, Queries/, Commands/, EventHandlers/
Technology (How)Class SuffixEfCore, InMemory, Dapper

File Name Pattern: {Subject}{Role}{Variant}

Section titled “File Name Pattern: {Subject}{Role}{Variant}”
File TypePatternExample
Repository{Aggregate}Repository{Variant}.csAIModelRepositoryInMemory.cs, AIModelRepositoryEfCore.cs
Query{Aggregate}Query{Variant}.csAIModelQueryInMemory.cs, DeploymentDetailQueryInMemory.cs
DB Model{Aggregate}.Model.csAIModel.Model.cs, Deployment.Model.cs
EF Config{Aggregate}.Configuration.csAIModel.Configuration.cs
UnitOfWorkUnitOfWork{Variant}.csUnitOfWorkInMemory.cs, UnitOfWorkEfCore.cs

Three Registration classes independently register each Adapter’s services.

Registration ClassRegistered Items
AdapterPresentationRegistrationFastEndpoints
AdapterPersistenceRegistrationRepository, Query, UnitOfWork (Observable wrappers)
AdapterInfrastructureRegistrationMediator, FluentValidation, OpenTelemetry, Pipeline, Domain Service, External Service
// Host Program.cs
var builder = WebApplication.CreateBuilder(args);
builder.Services
.RegisterAdapterPresentation()
.RegisterAdapterPersistence(builder.Configuration)
.RegisterAdapterInfrastructure(builder.Configuration);
var app = builder.Build();
app.UseAdapterPresentation();
app.Run();
public partial class Program { } // Integration Test support

The Persistence:Provider value in appsettings.json switches between InMemory and Sqlite.

{
"Persistence": {
"Provider": "InMemory",
"ConnectionString": "Data Source=ai-governance.db"
}
}

AdapterPersistenceRegistration branches based on the Provider value:

switch (options.Provider)
{
case "Sqlite":
services.AddDbContext<GovernanceDbContext>(opt =>
opt.UseSqlite(options.ConnectionString));
RegisterSqliteRepositories(services);
break;
case "InMemory":
default:
RegisterInMemoryRepositories(services);
break;
}

Since DI registration uses Observable wrappers, observability is automatically maintained even when switching providers:

// InMemory
services.RegisterScopedObservablePort<IAIModelRepository, AIModelRepositoryInMemoryObservable>();
// Sqlite
services.RegisterScopedObservablePort<IAIModelRepository, AIModelRepositoryEfCoreObservable>();

OpenTelemetry 3-Pillar observability is configured using RegisterOpenTelemetry + ConfigurePipelines.

services
.RegisterOpenTelemetry(configuration, AssemblyReference.Assembly)
.ConfigurePipelines(pipelines => pipelines
.UseObservability() // Batch-enable CtxEnricher, Metrics, Tracing, Logging
.UseValidation()
.UseException())
.Build();

UseObservability() batch-enables 4 observability components (CtxEnricher, Metrics, Tracing, Logging). The remaining pipelines are registered via explicit opt-in:

OrderMiddlewareRole
1UseObservability()Batch-enable CtxEnricher + Metrics + Tracing + Logging
2UseValidation()FluentValidation-based request validation
3UseException()Exception -> DomainError/AdapterError conversion
{
"OpenTelemetry": {
"ServiceName": "AiGovernance",
"ServiceNamespace": "AiGovernance",
"CollectorEndpoint": "http://localhost:18889",
"CollectorProtocol": "Grpc",
"SamplingRate": 1.0,
"EnablePrometheusExporter": false
}
}

Four LanguageExt IO patterns used in external service integration.

PatternImplementation ClassPurposeCore Method
Timeout + CatchModelHealthCheckServiceHealth check timeout handlingIO.Timeout(10s) -> .Catch(TimedOut, fallback) -> .Catch(Exceptional, error)
Retry + ScheduleModelMonitoringServiceExponential backoff retryIO.Retry(exponential(100ms) | jitter(0.3) | recurs(3) | maxDelay(5s))
Fork + awaitAllParallelComplianceCheckServiceParallel compliance checks on 5 criteriaforks.Map(io => io.Fork()) -> awaitAll(forks)
BracketModelRegistryServiceResource lifecycle management (session)acquire.Bracket(Use: ..., Fin: ...)

All external services have observability automatically added via [GenerateObservablePort], and are composed into the Application Layer’s FinT LINQ chain through IO<A> -> FinT<IO, A> conversion.


Terminal window
# Build
dotnet build Docs.Site/src/content/docs/samples/ai-model-governance/AiModelGovernance.slnx
# Test (268 tests)
dotnet test --solution Docs.Site/src/content/docs/samples/ai-model-governance/AiModelGovernance.slnx