Skip to content

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:

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.

mermaid
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:

cs
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();
  1. Configure logging via a custom extension.
  2. Register OpenAPI (for Swagger, not setup here).
  3. Register all dependencies for the feature services, in a custom IServiceCollection extension
    1. Including strategy provider and concrete classes, cache, validation, behaviours, ...
  4. Apply middleware.
  5. Define all of the API endpoints
    1. Currently per feature, can be moved to an extension and/or the feature itself.
  6. 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 Core DI container.
  • Scrutor for scanning and registering strategy implementations.

API Documentation

  • Microsoft.AspNetCore.OpenApi package.
  • OpenAPI endpoint 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 FrozenDictionary lookups 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:

  1. LoggingBehavior
  2. ValidationBehavior
  3. CachingBehavior
  4. TransactionBehavior
  5. Handler

Outside-in execution means the first registered behavior is the outermost layer; response unwinds in reverse.

mermaid
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: response

CQRS + 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.
mermaid
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 strategy

Middleware 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.

mermaid
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.
mermaid
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

  1. Add or update request contract in Commands or Queries.
  2. Implement corresponding handler.
  3. Add validation rules if needed.
  4. If request is cacheable, implement ICacheable.
  5. Map or update endpoint wiring in ModuleEndpoints.
  6. 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.

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

  1. Better feature locality. Most changes for a module-related use-case are contained in one feature folder tree.

  2. Easier onboarding. Developers can navigate by business capability (Modules) rather than deciphering deep horizontal layers.

  3. 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.

  4. Safer parallel development. Teams can work in different vertical slices with fewer merge collisions than shared global service files.

  5. 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

mermaid
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.


  1. caching is TBD ↩︎

  2. the transaction behaviour requires data to be written to somewhere, currently it isn't. ↩︎