Architecture

Patterns architecturaux de Place API - Modular Monolith, CQRS, Vertical Slices et pipeline MediatR

Architecture

Pattern Modular Monolith

Place API est un Monolithe Modulaire : une application deployee comme un seul processus, mais organisee en modules independants qui respectent des frontieres strictes.

Pourquoi un Monolithe Modulaire ?

  • Simplicite de deploiement : un seul artefact a deployer, pas d'orchestration de microservices
  • Consistance transactionnelle : les modules partagent la meme base de donnees PostgreSQL
  • Performance : communication in-process (pas de latence reseau entre modules)
  • Evolution : chaque module peut etre extrait en microservice si necessaire

Regles d'isolation

  1. Les types de chaque module sont internal par defaut
  2. Seuls les types des projets Contracts sont public
  3. Un module ne peut jamais referencer les types internes d'un autre module
  4. La communication se fait via les projets Contracts (DTOs, interfaces) ou les notifications MediatR
mermaid
flowchart TB
    API[API Host<br/>Program.cs] --> Identity[Module Identity<br/>schema: identity]
    API --> Audit[Module Audit<br/>schema: audit]
    API --> Messaging[Module Messaging<br/>schema: messaging]
    Identity --> BB[BuildingBlocks<br/>Bibliotheque partagee]
    Audit --> BB
    Messaging --> BB
    BB --> PG[(PostgreSQL<br/>schema-per-module)]

    Identity -.->|Contracts| Messaging
    Identity -.->|Notifications MediatR| Audit

Enregistrement des modules

Chaque module expose deux methodes d'extension dans InfrastructureExtensions :

  • AddInfrastructure(WebApplicationBuilder) : enregistre les services, DbContext, MediatR, options
  • UseInfrastructure(WebApplication) : configure le middleware, les endpoints SignalR

Dans Program.cs, les modules sont enregistres sequentiellement :

IdentityInfra.AddInfrastructure(builder);
AuditInfra.AddInfrastructure(builder);
MessagingInfra.AddInfrastructure(builder);

CQRS avec MediatR

Le projet utilise le pattern Command Query Responsibility Segregation (CQRS) implemente via MediatR 14.

Wrappers CQRS

Les BuildingBlocks definissent des interfaces fines qui encapsulent MediatR :

// Commandes (operations d'ecriture)
public interface ICommand<out T> : IRequest<T> where T : notnull { }
public interface ICommand : ICommand<Unit> { }

// Queries (operations de lecture)
public interface IQuery<out T> : IRequest<T> where T : notnull { }

// Handlers
public interface ICommandHandler<in TCommand, TResponse>
    : IRequestHandler<TCommand, TResponse>
    where TCommand : ICommand<TResponse>
    where TResponse : notnull { }

public interface IQueryHandler<in TQuery, TResponse>
    : IRequestHandler<TQuery, TResponse>
    where TQuery : IQuery<TResponse>
    where TResponse : notnull { }

Exemple concret : Login

// Commande
public sealed record LoginCommand(string Email, string Password, string DeviceId)
    : ICommand<Result<AuthTokensResponse>>;

// Handler
internal sealed class LoginCommandHandler(
    UserManager<User> userManager,
    IAuthTokenResponseBuilder tokenResponseBuilder,
    /* ... */)
    : ICommandHandler<LoginCommand, Result<AuthTokensResponse>>
{
    public async Task<Result<AuthTokensResponse>> Handle(
        LoginCommand request, CancellationToken cancellationToken)
    {
        // Logique metier
    }
}

Dispatch depuis les endpoints

Les endpoints utilisent toujours IMediator.Send() pour dispatcher :

AuthTokensResponse result = await mediator.Send(new LoginCommand(email, password, deviceId));

Vertical Slice Architecture

Chaque fonctionnalite est une "tranche verticale" autonome regroupant tous les elements necessaires dans un seul dossier :

Features/Auth/Login/V1/
├── Login.cs           # Contient : LoginCommand, LoginCommandValidator, LoginCommandHandler
└── LoginEndpoint.cs   # Contient : LoginEndpoint (IMinimalEndpoint)

Avantages

  • Cohesion forte : tout le code d'une fonctionnalite est au meme endroit
  • Couplage faible : les fonctionnalites ne dependent pas les unes des autres
  • Facilite de navigation : on trouve facilement le code en parcourant l'arborescence

Interface IMinimalEndpoint

Chaque endpoint implemente IMinimalEndpoint des BuildingBlocks :

public interface IMinimalEndpoint
{
    IEndpointRouteBuilder MapEndpoint(IEndpointRouteBuilder builder);
}

Les endpoints sont decouverts automatiquement au demarrage via AddMinimalEndpoints() qui scanne les assemblies des modules.

Pipeline MediatR

Le pipeline MediatR intercepte chaque requete avant qu'elle n'atteigne le handler. Plusieurs behaviors sont chaines :

mermaid
sequenceDiagram
    participant C as Client HTTP
    participant E as Endpoint
    participant M as MediatR
    participant V as ValidationBehavior
    participant A as AuditCommandBehavior
    participant H as Handler
    participant DB as DbContext

    C->>E: HTTP Request
    E->>M: Send(Command)
    M->>V: Pipeline Behavior
    V->>V: Validation FluentValidation
    alt Validation echouee
        V-->>E: ValidationException
        E-->>C: 400 Bad Request
    else Validation reussie
        V->>A: Suivant dans le pipeline
        A->>H: Handle(command, ct)
        H->>DB: Query / Save
        DB-->>H: Resultat
        H-->>A: Response
        A->>A: Emettre AuditEventV1
        A-->>E: Response
        E-->>C: HTTP Response
    end

ValidationBehavior

Defini dans BuildingBlocks/Validation/ValidationBehavior.cs, ce behavior :

  1. Resout un IValidator<TRequest> depuis le conteneur DI
  2. Si un validateur existe, execute la validation
  3. Si la validation echoue, leve une exception avant que le handler ne soit appele
  4. Si pas de validateur enregistre, passe directement au handler suivant

AuditCommandBehavior

Defini dans BuildingBlocks/Auditing/AuditCommandBehavior.cs, ce behavior :

  1. Verifie si la requete est une ICommand<T> (les queries ne sont pas auditees)
  2. Verifie si l'attribut [SkipAudit] est present
  3. Capture le contexte d'execution (utilisateur, IP, correlation ID, trace)
  4. Execute le handler
  5. Emet un AuditEventV1 avec le resultat (success ou failure)
  6. L'emission d'audit ne bloque jamais le flux metier (les erreurs sont loguees)

AuditHttpMiddleware

En complement du behavior CQRS, le middleware HTTP capture toutes les requetes HTTP :

  • Methode, chemin, route template
  • Codes de reponse
  • Corps de requete/reponse (optionnel, configurable)
  • Duree de traitement
  • Classification automatique (Activity, Security, Exception)

MessagingPersistenceBehavior

Specifique au module Messaging, ce behavior :

  1. Cree un enregistrement SentMessage avec le statut Pending
  2. Execute le handler (envoi reel du message)
  3. Met a jour le statut en Sent ou Failed selon le resultat
  4. Persiste toujours le resultat, meme en cas d'erreur

Securite HTTP

L'API applique des en-tetes de securite sur toutes les reponses :

  • X-Content-Type-Options: nosniff
  • X-Frame-Options: DENY
  • Referrer-Policy: strict-origin-when-cross-origin
  • Permissions-Policy: camera=(), microphone=(), geolocation=()
  • Content-Security-Policy (adapte pour Swagger/Scalar)
  • X-Correlation-Id (identifiant de correlation unique par requete)

Rate Limiting

Le module Identity configure un rate limiting granulaire par endpoint :

PolitiqueCible
RegistrationInscription (par IP)
AuthLogin, refresh, logout (par utilisateur authentifie)
SessionsReadLecture de sessions
SessionsRevokeRevocation de sessions
AdminSessionsOperations admin sur les sessions
TwoFactorOperations 2FA
GlobalLimite globale sur toutes les requetes

Chaque politique utilise un Fixed Window Limiter configurable via appsettings.json.