ADR-0002 : CQRS via un pipeline de comportements MediatR
| Statut | Accepté |
| Date | 2025-10 |
| Décideurs | Équipe de développement Edukit |
| Périmètre | Edukit-OrgService, Edukit-VM |
Contexte
Une fois la Clean Architecture retenue, il reste à structurer la couche Application. Chaque cas d’usage (créer un utilisateur, révoquer une licence, provisionner une VM) déclenche les mêmes préoccupations transverses :
- valider les entrées avant tout effet de bord ;
- journaliser l’exécution de façon homogène ;
- résoudre l’identité de l’appelant (utilisateur local correspondant au JWT) ;
- appliquer une autorisation fine, au-delà du simple contrôle de rôle au point d’entrée ;
- enfin, exécuter la logique métier.
Si ces étapes sont codées à la main dans chaque gestionnaire, elles finissent par diverger : un oubli de contrôle d’autorisation devient une vulnérabilité (OWASP A01, contrôle d’accès cassé). Il faut un mécanisme qui rende ces étapes systématiques et impossibles à oublier.
Par ailleurs, les opérations de lecture et d’écriture ont des besoins différents (les lectures veulent de la pagination et pas de suivi de changements EF Core ; les écritures veulent validation et autorisation). Une séparation explicite Commandes / Requêtes clarifie ces intentions.
Décision
Adopter CQRS (séparation Commandes / Requêtes) implémenté avec MediatR, et faire passer chaque requête par un pipeline de comportements ordonné :
Validation → Journalisation → Résolution de l'appelant → Autorisation → Gestionnaire
- Commandes : intention d’écriture, validée par FluentValidation (
*CommandValidator) exécuté dans leValidationBehavioravant tout accès à la base. - Requêtes : intention de lecture, sans suivi de changements, paginées via une enveloppe uniforme
PagedResult<T>(Items, TotalCount, Page, PageSize). - Autorisation co-localisée : chaque commande sensible possède un
IAuthorizationHandler<T>placé à côté d’elle. L’AuthorizationBehaviorapplique le principe d’échec immédiat : il refuse d’exécuter une commande pour laquelle aucun gestionnaire d’autorisation n’est enregistré. Les commandes réellement ouvertes enregistrent explicitement unAllowAllAuthorizationHandler<T>.
Cette discipline garantit qu’aucune route ne peut atteindre un gestionnaire métier sans qu’au moins une politique d’autorisation ait été évaluée.
Conséquences
Bénéfices
- Les préoccupations transverses sont écrites une fois et appliquées partout. Ajouter une commande, c’est hériter automatiquement de la validation, de la journalisation et du garde-fou d’autorisation.
- Le contrôle d’accès cassé est éliminé par construction : oublier l’autorisation provoque une exception au démarrage du traitement, pas une faille silencieuse.
- Chaque validateur est testable unitairement (un fichier
*ValidatorTests.csco-localisé, une assertion par règle). - La séparation lecture / écriture clarifie les intentions et autorise des optimisations ciblées (lectures sans suivi, pagination obligatoire).
Coûts et limites
- Indirection supplémentaire : suivre un appel demande de connaître le pipeline. Compensé par l’homogénéité et par la corrélation OpenTelemetry (entrée HTTP → comportement → gestionnaire → requête EF Core).
- MediatR devient une dépendance structurante de la couche Application. Le risque est limité : le patron médiateur est remplaçable, le pipeline pourrait être réimplémenté sans MediatR si nécessaire.
Alternatives écartées
- Appels de services applicatifs directs (pas de médiateur) : moins d’indirection, mais les préoccupations transverses redeviennent manuelles et donc oubliables. Le garde-fou d’autorisation par échec immédiat n’existerait plus.
- Filtres de point d’entrée / middleware pour l’autorisation : insuffisant pour l’autorisation fine (borner un administrateur à son établissement) qui dépend du contenu de la commande, pas seulement du rôle dans le jeton.
- CQRS avec bases de lecture / écriture séparées (event sourcing complet) : surdimensionné. Edukit n’a pas besoin de modèles de lecture matérialisés ni de rejeu d’événements pour son métier ; la séparation logique des commandes et des requêtes suffit.
Références
- Dossier B2, EADL-B2-C2 : « Patrons de code défensif » et EADL-B2-C4 : « Pile technologique partagée et patrons CQRS ».
AuthorizationBehavior.cs,CreateUserCommandValidator.cs,CreateUserAuthorizationHandler.cs(OrgService).- Voir aussi ADR-0001.