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
- Les types de chaque module sont
internalpar defaut - Seuls les types des projets
Contractssontpublic - Un module ne peut jamais referencer les types internes d'un autre module
- La communication se fait via les projets
Contracts(DTOs, interfaces) ou les notifications MediatR
Enregistrement des modules
Chaque module expose deux methodes d'extension dans InfrastructureExtensions :
AddInfrastructure(WebApplicationBuilder): enregistre les services, DbContext, MediatR, optionsUseInfrastructure(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 :
ValidationBehavior
Defini dans BuildingBlocks/Validation/ValidationBehavior.cs, ce behavior :
- Resout un
IValidator<TRequest>depuis le conteneur DI - Si un validateur existe, execute la validation
- Si la validation echoue, leve une exception avant que le handler ne soit appele
- Si pas de validateur enregistre, passe directement au handler suivant
AuditCommandBehavior
Defini dans BuildingBlocks/Auditing/AuditCommandBehavior.cs, ce behavior :
- Verifie si la requete est une
ICommand<T>(les queries ne sont pas auditees) - Verifie si l'attribut
[SkipAudit]est present - Capture le contexte d'execution (utilisateur, IP, correlation ID, trace)
- Execute le handler
- Emet un
AuditEventV1avec le resultat (success ou failure) - 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 :
- Cree un enregistrement
SentMessageavec le statutPending - Execute le handler (envoi reel du message)
- Met a jour le statut en
SentouFailedselon le resultat - 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: nosniffX-Frame-Options: DENYReferrer-Policy: strict-origin-when-cross-originPermissions-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 :
| Politique | Cible |
|---|---|
Registration | Inscription (par IP) |
Auth | Login, refresh, logout (par utilisateur authentifie) |
SessionsRead | Lecture de sessions |
SessionsRevoke | Revocation de sessions |
AdminSessions | Operations admin sur les sessions |
TwoFactor | Operations 2FA |
Global | Limite globale sur toutes les requetes |
Chaque politique utilise un Fixed Window Limiter configurable via appsettings.json.