CQRS: commande valeurs de retour
il semble y avoir une confusion sans fin sur la question de savoir si les commandes devraient ou non avoir des valeurs de retour. J'aimerais savoir si la confusion est attribuable simplement au fait que les participants n'ont pas indiqué leur contexte ou leur situation.
La Confusion
Voici des exemples de la confusion...
Udi Dahan dit que les commandes "ne renvoient pas les erreurs au client", mais dans le même article il montre un diagramme où les commandes en effet, retourner les erreurs au client.
un article de Microsoft Press Store indique "la commande...ne retourne pas une réponse", mais ensuite pour donner un ambiguë attention:
au fur et à mesure que l'expérience du champ de bataille se développe autour des RQC, certaines pratiques se consolident et tendent à devenir des pratiques exemplaires. En partie contraire à ce que nous venons de dire... il est courant aujourd'hui de penser que le gestionnaire de commandes et l'application ont tous deux besoin de savoir comment opération transactionnelle terminée. Les résultats doivent être connus...
- Jimmy Bogard dit"commandes ont toujours un résultat " mais fait alors un effort supplémentaire pour montrer comment les commandes retournent void.
Eh bien, est-ce que les gestionnaires de commandes renvoient des valeurs ou non?
La Réponse?
s'inspirant de Jimmy Bogard " mythes CQRS" je pense que la réponse à cette question dépend de ce que programmatique/contextuelle "quadrant" vous parlez de:
+-------------+-------------------------+-----------------+
| | Real-time, Synchronous | Queued, Async |
+-------------+-------------------------+-----------------+
| Acceptance | Exception/return-value* | <see below> |
| Fulfillment | return-value | n/a |
+-------------+-------------------------+-----------------+
Acceptation (par exemple, la validation)
commande "acceptation" renvoie principalement à la validation. On peut supposer que les résultats de validation doivent être donnés de façon synchrone à l'appelant, que la commande "fulfillment" soit synchrone ou en file d'attente.
cependant, il semble que de nombreux praticiens n'initient pas la validation à partir du gestionnaire de commandes. De ce que j'ai vu, c'est soit parce que (1) Ils ont déjà trouvé une façon fantastique de gérer validation à la couche application (c.-à-d. ASP.NET MVC controller checking valid state via data annotations) ou (2) une architecture est en place qui suppose que les commandes sont soumises à un bus ou une file d'attente (hors processus). Ces dernières formes d'asynchronie n'offrent généralement pas de sémantique de validation synchrone ni d'interfaces.
en bref, beaucoup de concepteurs peuvent vouloir que le gestionnaire de commandes fournisse les résultats de validation comme une valeur de retour (synchrone), mais ils doivent vivre avec le restrictions des outils asynchrones qu'ils utilisent.
accomplissement
en ce qui concerne l ' "exécution" d'une commande, le client qui a émis la commande pourrait avoir besoin de connaître la scope_identity pour un enregistrement nouvellement créé ou peut - être des informations d'échec-comme "compte à découvert"."
dans un contexte de temps réel, il semble qu'une valeur de retour est la plus logique; les exceptions ne devraient pas être utilisées pour communiquer les résultats d'échec liés à l'entreprise. Cependant, dans un "queuing" cadre...les valeurs de retour n'ont naturellement aucun sens.
C'est là que toute la confusion peut être résumée:
Beaucoup (la plupart?) Les praticiens du CQRS supposent qu'ils vont maintenant, ou à l'avenir, incorporer un cadre ou une plate-forme asynchrone (un bus ou une file d'attente) et proclament ainsi que les manipulateurs de commandes n'ont pas de valeurs de retour. Cependant, certains praticiens n'ont pas l'intention d'utiliser ces concepts basés sur des événements, et ils approuveront donc les gestionnaires de commandes qui valeurs de retour (synchrones).
ainsi, par exemple, je crois qu'un contexte synchrone (request-response) a été supposé quand Jimmy Bogard a fourni cet exemple d'interface de commande:
public interface ICommand<out TResult> { }
public interface ICommandHandler<in TCommand, out TResult>
where TCommand : ICommand<TResult>
{
TResult Handle(TCommand command);
}
son produit Mediatr est, après tout, un outil en mémoire. Compte tenu de tout cela, je pense que la raison Jimmysoigneusement pris le temps de produire un vide de retour d'une commande n'était pas parce que "les manipulateurs de commandes ne devraient pas avoir de valeurs de retour", mais à la place parce qu'il voulait simplement que sa classe Mediator ait une interface cohérente:
public interface IMediator
{
TResponse Request<TResponse>(IQuery<TResponse> query);
TResult Send<TResult>(ICommand<TResult> query); //This is the signature in question.
}
...même si toutes les commandes n'ont pas une valeur significative à retourner.
Répéter et terminer
est-ce que je capte correctement pourquoi il y a de la confusion à ce sujet? Est-il quelque chose que je suis absent?
4 réponses
suivre le conseil dans affronter la Complexité dans CQRS par Vladik Khononov suggère que la manipulation de commande peut retourner des informations relatives à son résultat.
sans violer aucun principe [CQRS], une commande peut retourner en toute sécurité les données suivantes:
- résultat D'exécution: succès ou échec;
- messages D'erreur ou erreurs de validation, en cas d'échec;
- le numéro de la nouvelle version de l'agrégat, en cas de succès;
cette information améliorera considérablement l'expérience utilisateur de votre système, car:
- Vous n'avez pas à interroger une source externe pour l'exécution d'une commande suite, vous avez tout de suite. Il devient trivial de valider des commandes et de retourner des messages d'erreur.
- si vous voulez rafraîchir les données affichées, vous pouvez utiliser la nouvelle version de l'agrégat pour déterminer si le modèle de vue reflète ou non la commande exécutée. Aucun de plus l'affichage des données périmées.
Daniel Whittaker, qui prône le retour à un "commune" objet à partir d'un gestionnaire de commande contenant cette information.
Eh bien, est-ce que les gestionnaires de commandes renvoient des valeurs ou non?
Ils ne devraient pas retourner Données Sur Les Entreprises, seuls les méta-données (concernant le succès ou l'échec de l'exécution de la commande). CQRS
CQS prise à un niveau supérieur. Même si tu enfreins les règles du puriste et que tu rends quelque chose, que retourneras-tu? Dans CQRS le handler de commande est une méthode de application service
chargement aggregate
appelle alors une méthode sur le aggregate
puis il persiste aggregate
. L'intention du gestionnaire de commande est de modifier le aggregate
. Vous ne savez pas ce retour qui serait indépendant de l'appelant. Chaque appelant/client du gestionnaire de commandes voudrait savoir quelque chose d'autre sur le nouvel état.
si l'exécution de la commande bloque (alias synchrone), alors tout ce que vous devez savoir, c'est si la commande a été exécutée avec succès ou non. Puis, dans une couche supérieure, vous interrogez la chose exacte que vous devez savoir sur la nouvelle application de l'état à l'aide d'une requête-modèle qui est le mieux adapté à vos besoins.
pensez autrement, si vous renvoyez quelque chose d'un gestionnaire de commandes, vous lui donnez deux responsabilités: 1. modifier l'état agrégé et 2. requête de certains lisent-modèle.
en ce qui concerne la validation des commandes, il existe au moins deux types de validation des commandes:
- contrôle de santé de commande, qui vérifie qu'une commande a les données correctes (c.-à-d. une adresse e-mail est valide); ceci est fait avant que la commande atteigne l'agrégat, dans le gestionnaire de commandes (le service d'application) ou dans le constructeur de commandes;
- vérification des invariants de domaine, qui est effectuée à l'intérieur de l'agrégat, après que la commande atteigne l'agrégat (après qu'une méthode est appelée sur l'agrégat) et elle vérifie que l'agrégat peut muter vers le nouvel état.
cependant, si nous montons un certain niveau, dans le Presentation layer
(c'est à dire un REST
endpoint), le client du Application layer
, nous vous pouvez retourner n'importe quoi et nous n'enfreindrons pas les règles parce que les endpoints sont conçus après les cas d'utilisation, vous savez exactement ce que vous voulez retourner après l'exécution d'une commande, dans chaque cas d'utilisation.
CQRS et CQS sont comme des microservices et la décomposition de classe: l'idée principale est la même ("tendent à de petits modules cohésifs"), mais ils se trouvent sur des niveaux sémantiques différents.
le but de CQRS est de faire une séparation entre les modèles en écriture et en lecture; de tels détails de bas niveau comme la valeur de retour d'une méthode spécifique ne sont absolument pas pertinents.
prenez note de ce qui suit citation de Fowler:
le changement introduit par le CQRS est de diviser modèle conceptuel en modèles séparés pour la mise à jour et l'affichage, qu'il se réfère comme commande et requête respectivement en suivant le vocabulaire de la séparation de la commande.
C'est sur modèles, pas méthode.
le gestionnaire de commandes peut renvoyer n'importe quoi sauf les modèles de lecture: état (succès/échec), événements générés (c'est le but principal des gestionnaires de commandes, btw: générer des événements pour la commande donnée), erreurs. Manipulateurs de commandes très souvent jeter exception non contrôlée, il est un exemple de signaux de sortie des gestionnaires de commandes.
D'ailleurs, L'auteur du terme, Greg Young, dit que les commandes sont toujours synchronisées (sinon, cela devient événement): https://groups.google.com/forum/#!topic/dddcqrs/xhJHVxDx2pM
Greg Young
en fait, j'ai dit qu'une commande asynchrone n'existe pas :) sa fait un autre événement.
réponse pour @Constantin Galbenu, j'ai affronté limit.
@Misanthrope et que faites-vous exactement avec ces événements?
@Constantin Galbenu, dans la plupart des cas, je n'en ai pas besoin suite à l'ordre, bien sûr. Dans certains cas -- je dois aviser le client en réponse à cette demande D'API.
Il est extrêmement utile lorsque:
- vous devez signaler une erreur via des événements plutôt que des exceptions. Il se produit généralement lorsque votre le modèle doit être sauvegardé (par exemple, il compte le nombre de tentatives avec le mauvais code/mot de passe) même si une erreur s'est produite. En outre, certains gars n'utilisent pas d'exceptions pour les erreurs d'affaires du tout -- seulement des événements (http://andrzejonsoftware.blogspot.com/2014/06/custom-exceptions-or-domain-events.html) il n'y a pas de raison particulière de penser que lancer des exceptions d'affaires depuis le gestionnaire de commandes est OK, mais retourner des événements de domaine n'est pas.
- Quand l'événement se produit seulement avec quelques circonstances à l'intérieur de votre racine agrégée.
Et je peux donner des exemple pour le deuxième cas. Imaginez que nous fassions un service semblable à Tinder, nous avons un commandement semblable àtranger. Cette commande peut avoir pour résultat une réponse Etrangère si nous aimons la personne qui nous a déjà aimé avant. Nous avons besoin d'informer le client mobile en réponse si la correspondance a eu lieu ou non. Si vous voulez juste vérifier matchQueryService après la commande, vous pouvez trouver match là, mais il n'y a pas de garantie que match qui s'est passé dès maintenant, parce que parfois Tinder montre déjà appariés étrangers (probablement, dans les zones non peuplées, peut-être incohérence, probablement vous avez juste 2nd dispositif, etc.).
vérifier la réponse si StrangersWereMatched vraiment arrivé en ce moment est si simple:
$events = $this->commandBus->handle(new LikeStranger(...));
if ($events->contains(StrangersWereMatched::class)) {
return LikeApiResponse::matched();
} else {
return LikeApiResponse::unknown();
}
Oui, vous pouvez introduire le code de commande, par exemple, et de faire Correspondre les lire de modèle pour le garder:
// ...
$commandId = CommandId::generate();
$events = $this->commandBus->handle(
$commandId,
new LikeStranger($strangerWhoLikesId, $strangerId)
);
$match = $this->matchQueryService->find($strangerWhoLikesId, $strangerId);
if ($match->isResultOfCommand($commandId)) {
return LikeApiResponse::matched();
} else {
return LikeApiResponse::unknown();
}
... mais pensez-y: pourquoi pensez-vous que le premier exemple avec la logique pure est pire?
Il ne viole pas CQRS de toute façon, j'ai juste fait l'implicite.
Il est apatride immuable approche. Moins de chances de toucher un bug (par exemple si matchQueryService
est mis en cache ou en différé [pas immédiatement compatibles], vous avez un problème).
Oui, lorsque le fait d'apparier n'est pas suffisant et que vous avez besoin d'obtenir des données pour répondre, vous devez utiliser le service d'interrogation. Mais rien ne vous empêche de recevoir des événements à partir du gestionnaire de commande.