Imparta.iCoachAi.Api Architecture Overview
Audience And Intent
This architectural overview is written for software developers who need a practical understanding of how the Imparta.iCoachAi.Api project has been designed. It explains how requests flow through the application, why the current architectural approach is useful, and how to easily create new features / functionality.
If you're creating a new Feature, or extending one, all the information is in How Do I....
This complements the technology-focused documentation in:
and the glossary:
The Big Picture
Imparta.iCoachAi.Api is an ASP.NET Core minimal API built on a modular architecture that combines:
- Minimal API endpoint mapping.
- CQRS pattern separating request, command and notification dispatch through a custom mediator.
- Pipeline behaviors for cross-cutting concerns. These are currently:
- Strategy pattern for implementation selection at runtime.
- Vertical slicing (aka vertical architecture) to focus on user-facing features and delivery.
- Middleware for manipulating the HTTPS request and responses.
- Observability-first logging with Open Telemetry setup with Serilog and Sentry.
- Logs are submitted to our on-site BugSink tool.
High Level Flow of Architecture
This chart details the flow for a vertical slice feature. These are located in the /Features/<feature> folders (as vertical slices), eg, /Features/Modules.
flowchart LR
A[HTTP Client] --> M[HTTP Middleware]
M --> B[ASP.NET Core Minimal API Host]
B --> C[Feature Endpoints]
C --> D[ISender / IPublisher]
D --> E[Dispatchers]
E --> F[Pipeline Behaviors]
F --> G[Request Handlers]
G --> H[Response]
C --> I[Strategy Endpoints]
I --> J[ModuleProvider]
J --> K[IModule Concrete Strategy]
B --> L[Serilog + Sentry + OTel Packages]Setup And Bootstrapping Approach
Entry Point
The application starts from the ususl Program.cs using the standard WebApplication builder model.
Startup sequence:
using Imparta.iCoachAi.Api.Extensions;
using Imparta.iCoachAi.Api.Features.Modules;
var builder = WebApplication.CreateBuilder(args);
// 1. Configure logging via a custom extension.
builder.Logging.AddICoachLogging(builder);
var services = builder.Services;
// 2. Register OpenAPI.
services.AddOpenApi();
// 3. Register all dependencies for the feature services, in a custom `IServiceCollection` extension
services.AddFeatureServices();
var app = builder.Build();
app.Logger.LogInformation("Imparta i-Coach API starting...");
if (app.Environment.IsDevelopment())
{
app.MapOpenApi();
}
app.UseHttpsRedirection();
// 4. Apply middleware.
app.UseCustomMiddleware(options =>
{
options.ResponseFormatExclude = ["/swagger"];
});
app.MapGet("/", () => "Hello World!\nTry api/modules");
// 5. Define all of the API endpoints
app.MapModuleEndpoints();
// 6. Run the host...
app.Run();- Configure logging via a custom extension.
- Register OpenAPI (for Swagger, not setup here).
- Register all dependencies for the feature services, in a custom
IServiceCollectionextension- Including strategy provider and concrete classes, cache, validation, behaviours, ...
- Apply middleware.
- Define all of the API endpoints
- Currently per feature, can be moved to an extension and/or the feature itself.
- Run the host...
What This Means For Developers
- Configuration is centralized and readable in one composition root.
- Feature registration is abstracted behind extension methods for maintainability.
- Endpoint mapping is grouped in feature modules rather than spread through startup code.
Technology Stack And Why It Is Used
Platform
- ASP.NET Core on net10.0 target framework.
- Minimal APIs for concise endpoint definitions and low ceremony.
Validation
FluentValidation+FluentValidation.DependencyInjectionExtensions.- Validation rules are expressed separately to the handlers and automatically validated via pipeline behavior.
Caching
Microsoft.Extensions.Caching.Hybrid.- Query requests can opt in to caching by implementing
ICacheable.
Dependency Injection And Assembly Scanning
- Built-in
ASP.NET CoreDI container. Scrutorfor scanning and registering strategy implementations.
API Documentation
Microsoft.AspNetCore.OpenApipackage.OpenAPIendpoint is mapped in the development environment.
Observability
- Serilog as the main structured logging engine.
- Serilog console sink for local visibility.
- Sentry sinks and OpenTelemetry integration for error and trace capture.
- OpenTelemetry exporter packages are present for telemetry pipeline growth.
Performance-Oriented Dispatching
- Custom dispatcher registry uses
FrozenDictionarylookups by request/notification runtime type. - Wrapper instances are built once at startup and reused per request.
Request Processing Pipeline (Cross-cutting concerns / Behaviours)
The project uses layered request processing for cross-cutting concerns.
Configured behavior order:
LoggingBehaviorValidationBehaviorCachingBehaviorTransactionBehaviorHandler
Outside-in execution means the first registered behavior is the outermost layer; response unwinds in reverse.
sequenceDiagram
participant EP as Endpoint
participant L as LoggingBehavior
participant V as ValidationBehavior
participant C as CachingBehavior
participant T as TransactionBehavior
participant H as Request Handler
EP->>L: Send(request)
L->>V: next()
V->>C: next()
C->>T: next()
T->>H: next()
H-->>T: response
T-->>C: response
C-->>V: response
V-->>L: response
L-->>EP: responseCQRS + Mediator-Style Approach
Core Idea
- Commands represent write intent.
- Queries represent read intent.
- Notifications represent in-process side effects.
How It Is Implemented
- Endpoints call ISender for commands and queries.
- Endpoints call IPublisher for notifications.
- Dispatcher performs runtime lookup and executes wrapper chains.
- Handlers remain focused on request-specific logic.
Why This Helps
- Endpoint logic stays thin and transport-focused.
- Cross-cutting rules are consistently enforced.
- Features remain easier to test and evolve independently.
Strategy Pattern In The Modules Domain
The Domain folder also implements a strategy model for executable module actions.
- IModule is the shared strategy contract.
- Concrete classes implement Execute().
- ModuleProvider discovers implementations and resolves one by name.
- /strategies endpoints expose available strategies and runtime execution.
classDiagram
class IModule {
<<interface>>
+Execute() void
}
class ModuleProvider {
+AvailableModules IEnumerable~string~
+Create(name string) IModule
}
class AzureSpeechToText
class AzureTextToSpeech
class Email
IModule <|.. AzureSpeechToText
IModule <|.. AzureTextToSpeech
IModule <|.. Email
ModuleProvider ..> IModule : returns selected strategyMiddleware And HTTP Surface
Middleware Characteristics
- HTTPS redirection is enabled.
- A custom middleware extension adds x-apisource response header.
- Request duration middleware is conditionally applied to endpoints marked with a Timer attribute.
- Response-format exclusion options are configurable through ApiMiddwareOptions.
Endpoint Shape
- Root endpoint provides a lightweight hello response.
- /api/modules endpoints provide CRUD-like module operations via CQRS dispatch.
- /strategies endpoints provide strategy discovery and execution.
Notification Handling Model
Notifications are published in-process and handled sequentially.
flowchart LR
A[Command handler completed] --> B[IPublisher.Publish]
B --> C[Dispatcher notification lookup]
C --> D[Notification wrapper]
D --> E[Handler 1]
E --> F[Handler 2]
F --> G[Done]This model is simple and deterministic, and can be evolved later if parallelization or external messaging is needed.
Code Organization
High-level folder responsibilities:
- Common: dispatcher abstractions, pipeline behaviors, shared interfaces and attributes.
- Extensions: host setup, service registration, logging, middleware composition.
- Features/Modules: endpoint definitions, commands, queries, notifications, domain strategies.
- Middleware: custom request/response middleware components and options.
flowchart TB
A[Imparta.iCoachAi.Api]
A --> B[Common]
A --> C[Extensions]
A --> D[Features]
A --> E[Middleware]
D --> D1[Modules]
D1 --> D2[Commands]
D1 --> D3[Queries]
D1 --> D4[Notifications]
D1 --> D5[Domain]Typical Developer Workflow
- Add or update request contract in Commands or Queries.
- Implement corresponding handler.
- Add validation rules if needed.
- If request is cacheable, implement ICacheable.
- Map or update endpoint wiring in ModuleEndpoints.
- Add notification publishing and handlers for side effects where required.
Operational Notes
- OpenAPI is available in development mode.
- Logging includes environment-context enrichment.
- Sentry integration is configured for event capture and tracing.
- Current module data and transaction scaffolding indicate an architecture prepared for persistence-layer expansion.
Related Documents
Vertical Architecture In This Project
This codebase follows a vertical-slice style inside the feature area, centered around use-cases rather than technical layers.
What Vertical Architecture Means Here
A vertical slice groups everything needed for one feature flow in one place. Instead of splitting all commands, all services, and all controllers globally, each feature owns its endpoint contracts, request models, handlers, and supporting domain behavior.
In this project, the Modules feature is the clearest example:
- Endpoints: route definitions and HTTP behavior in
ModuleEndpoints. - Commands: write-intent contracts and handlers in
Features/Modules/Commands. - Queries: read-intent contracts and handlers in
Features/Modules/Queries. - Notifications: side-effect reactions in
Features/Modules/Notifications. - Domain strategy logic: runtime module execution in
Features/Modules/Domain.
Why This Is Useful For Developers
Better feature locality. Most changes for a module-related use-case are contained in one feature folder tree.
Easier onboarding. Developers can navigate by business capability (Modules) rather than deciphering deep horizontal layers.
Lower coupling between features. Feature internals are less likely to leak into unrelated areas because each slice owns its own command/query contracts and handlers.
Safer parallel development. Teams can work in different vertical slices with fewer merge collisions than shared global service files.
Natural fit with CQRS. Commands and queries already represent use-cases, so storing them under a feature slice reinforces the same mental model.
Slice Composition Model
flowchart TB
A[Feature Slice: Modules]
A --> B[ModuleEndpoints]
A --> C[Commands]
A --> D[Queries]
A --> E[Notifications]
A --> F[Domain Strategies]
C --> C1[CreateCommand + Handler]
C --> C2[UpdateCommand + Handler]
C --> C3[DeleteCommand + Handler]
D --> D1[ListQuery + Handler]
D --> D2[GetByIdQuery + Handler]
E --> E1[ModuleCreatedNotification]
E --> E2[AuditLogHandler]
F --> F1[IModule]
F --> F2[ModuleProvider]
F --> F3[AzureSpeechToText / AzureTextToSpeech / Email]Vertical Architecture Boundaries In Practice
- Horizontal platform concerns still exist in shared places (
Common,Extensions,Middleware) and are intentionally reused by all slices. - Business capability code lives under feature slices and should prefer intra-slice cohesion.
- Shared abstractions are used when they are truly cross-cutting (dispatcher interfaces, pipeline behaviors, middleware), not as a default for all logic.
Practical Rule Of Thumb For New Work
When adding a new capability, start by creating a new feature slice (or extending an existing one) with endpoint + command/query + handler(s) first, then pull out shared code only when multiple slices genuinely need it.