UC-08 · Une command modifie des aggregates, une query lit des views
Signal
- Un use case qui modifie un aggregate et renvoie ce qu’un écran affiche.
- Un query handler qui charge un aggregate par son repository pour construire ce qu’il renvoie.
- Un command handler qui importe un view port, ou un query handler qui importe un repository.
Pourquoi
Poser une question ne doit pas changer la réponse, et changer l’état ne doit pas dépendre de ce qu’un écran veut montrer. Une command charge des aggregates, les laisse décider, et enregistre le résultat. Une query lit ce qu’elle montre, et ne change rien.
Séparé, chaque côté reste simple : les aggregates sont taillés pour leurs invariants, pas pour l’affichage, et une view est taillée pour son écran, pas pour le modèle.
Exceptions
Un use case de création renvoie l’identité qu’il a créée, pour que l’appelant puisse en lire la view.
Correction
Séparer le use case : une command qui modifie l’aggregate et renvoie Result<void, E>, ou l’identité qu’elle a créée, puis une query qui lit la view.
Adaptation
Notre choix : une command renvoie Result<void, E>. Ce n’est pas une valeur métier : le Result dit seulement si la command a été refusée, et command et query restent séparées.
Notre choix : la couche application se compose de command handlers et de query handlers, sur les mêmes tables (pas de stockage CQRS, CTX-16). Une command charge des aggregates par leurs repositories, appelle une méthode métier, et enregistre. Une query lit des views (*.view.ts) à travers un view port (*-views.port.ts) implémenté dans driven, et ne charge jamais d’aggregate.
Un bounded context en transaction scripts n’a ni objet command, ni objet query : chaque méthode de son service est l’un ou l’autre (TXS-01).