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 le ValidationBehavior avant 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’AuthorizationBehavior applique 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 un AllowAllAuthorizationHandler<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.cs co-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.

Retour en haut