J'adapte le CQRS 1 du pauvre depuis un certain temps maintenant parce que j'aime sa flexibilité d'avoir des données granulaires dans un magasin de données, offrant de grandes possibilités d'analyse et augmentant ainsi la valeur commerciale et, si nécessaire, un autre pour les lectures contenant des données dénormalisées pour des performances accrues .
Mais malheureusement, depuis le début, je me bats avec le problème où je devrais placer la logique métier dans ce type d'architecture.
D'après ce que je comprends, une commande est un moyen de communiquer l'intention et n'a pas de liens avec un domaine en soi. Ce sont essentiellement des objets de transfert de données (stupides - si vous le souhaitez). Il s'agit de rendre les commandes facilement transférables entre différentes technologies. Il en va de même pour les événements en tant que réponses aux événements terminés avec succès.
Dans une application DDD typique, la logique métier réside au sein d'entités, d'objets de valeur, de racines agrégées, elles sont riches à la fois en données et en comportement. Mais une commande n'est pas un objet de domaine, elle ne doit donc pas se limiter aux représentations de données de domaine, car cela met trop de pression sur elles.
La vraie question est donc: où est exactement la logique?
J'ai découvert que j'ai tendance à faire face à cette lutte le plus souvent lorsque j'essaie de construire un agrégat assez compliqué qui établit des règles sur les combinaisons de ses valeurs. De plus, lors de la modélisation d'objets de domaine, j'aime suivre le paradigme de l' échec rapide , sachant qu'un objet atteint une méthode, il est dans un état valide.
Supposons qu'un agrégat Car
utilise deux composants:
Transmission
,Engine
.
Les deux Transmission
et les Engine
objets de valeur sont représentés comme types de super et ont selon les types de sous, Automatic
et les Manual
transmissions, ou Petrol
et Electric
moteurs respectivement.
Dans ce domaine, vivre seul a réussi à créer Transmission
, que ce Automatic
soit ou Manual
, ou l'un ou l'autre type d'un Engine
est tout à fait bien. Mais l' Car
agrégat introduit quelques nouvelles règles, applicables uniquement lorsque Transmission
et les Engine
objets sont utilisés dans le même contexte. À savoir:
- Lorsqu'une voiture utilise un
Electric
moteur, le seul type de transmission autorisé estAutomatic
. - Lorsqu'une voiture utilise un
Petrol
moteur, elle peut avoir l'un ou l'autre typeTransmission
.
Je pourrais attraper cette violation de combinaison de composants au niveau de la création d'une commande, mais comme je l'ai déjà dit, d'après ce que je comprends, cela ne devrait pas être fait car la commande contiendrait alors une logique métier qui devrait être limitée à la couche domaine.
L'une des options est de déplacer cette validation de logique métier pour commander le validateur lui-même, mais cela ne semble pas non plus être correct. J'ai l'impression de déconstruire la commande, de vérifier ses propriétés récupérées à l'aide de getters et de les comparer dans le validateur et d'inspecter les résultats. Cela me crie comme une violation de la loi de Déméter .
En ignorant l'option de validation mentionnée car elle ne semble pas viable, il semble que l'on devrait utiliser la commande et en construire l'agrégat. Mais où cette logique devrait-elle exister? Doit-il appartenir au gestionnaire de commandes chargé de gérer une commande concrète? Ou devrait-il être dans le validateur de commandes (je n'aime pas non plus cette approche)?
J'utilise actuellement une commande et j'en crée un agrégat dans le gestionnaire de commandes responsable. Mais quand je fais cela, si j'avais un validateur de commande, il ne contiendrait rien du tout, car si la CreateCar
commande existait, elle contiendrait alors des composants dont je sais qu'ils sont valides dans des cas distincts mais l'agrégat pourrait dire différent.
Imaginons un scénario différent mélangeant différents processus de validation - création d'un nouvel utilisateur à l'aide d'une CreateUser
commande.
La commande contient un Id
des utilisateurs qui auront été créés et leur Email
.
Le système définit les règles suivantes pour l'adresse e-mail de l'utilisateur:
- doit être unique,
- ne doit pas être vide,
- doit contenir au plus 100 caractères (longueur maximale d'une colonne db).
Dans ce cas, même si avoir un e-mail unique est une règle commerciale, le vérifier dans son ensemble n'a pas beaucoup de sens, car je devrais charger l'ensemble des e-mails actuels dans le système dans une mémoire et vérifier l'e-mail dans la commande contre l'agrégat ( Eeeek! Quelque chose, quelque chose, la performance.). Pour cette raison, je déplacerais cette vérification vers le validateur de commande, qui prendrait UserRepository
comme dépendance et utiliserait le référentiel pour vérifier si un utilisateur avec l'e-mail présent dans la commande existe déjà.
Quand il s'agit de cela, il est soudain logique de mettre également les deux autres règles de messagerie dans le validateur de commande. Mais j'ai le sentiment que les règles doivent être réellement présentes dans un User
agrégat et que le validateur de commande ne doit vérifier que l'unicité et si la validation réussit, je dois continuer à créer l' User
agrégat dans le CreateUserCommandHandler
et le transmettre à un référentiel pour être enregistré.
Je me sens comme ça parce que la méthode de sauvegarde du référentiel est susceptible d'accepter un agrégat qui garantit qu'une fois l'agrégat passé, tous les invariants sont remplis. Lorsque la logique (par exemple, la non-vacuité) n'est présente que dans la validation de commande elle-même, un autre programmeur peut ignorer complètement cette validation et appeler directement la méthode de sauvegarde dans UserRepository
avec un User
objet, ce qui pourrait entraîner une erreur de base de données fatale, car l'e-mail peut avoir trop longtemps.
Comment gérez-vous personnellement ces validations et transformations complexes? Je suis surtout satisfait de ma solution, mais j'ai l'impression d'avoir besoin d'affirmer que mes idées et mes approches ne sont pas complètement stupides pour être assez satisfaites des choix. Je suis entièrement ouvert à des approches complètement différentes. Si vous avez quelque chose que vous avez personnellement essayé et qui a très bien fonctionné pour vous, j'aimerais voir votre solution.
1 En tant que développeur PHP responsable de la création de systèmes RESTful, mon interprétation de CQRS s'écarte un peu de l' approche standard de traitement des commandes asynchrones , comme le retour parfois des résultats de commandes en raison du besoin de traiter les commandes de manière synchrone.
CommandDispatcher
.