98 votes

CQRS: Valeurs de retour de commande

Il semble être sans fin de la confusion quant à savoir si les commandes devrait ou ne devrait pas avoir des valeurs de retour. Je voudrais savoir si la confusion est tout simplement parce que les participants n'ont pas indiqué leur contexte ou les circonstances.

La Confusion

Voici des exemples de la confusion...

  • Udi Dahan, dit commandes ne sont pas "le retour d'erreurs pour le client," mais dans le même article , il montre un diagramme où les commandes en effet, les erreurs de retour au client.

  • Microsoft, Appuyez sur Store article stipule que "le commandement (...) ne renvoie pas de réponse", mais ensuite pour donner un ambiguë attention:

Comme champ de bataille, l'expérience se développe autour de CQRS, certaines pratiques de consolider et ont tendance à devenir de meilleures pratiques. En partie contraire à ce que nous venons de le mentionner... c'est un point de vue commun aujourd'hui de penser qu'à la fois le gestionnaire de commande et l'application nécessaire de savoir comment les opérations de transaction est allé. Les résultats doivent être connus...

Eh bien, ne les gestionnaires de commande les valeurs de retour ou pas?

La Réponse?

S'inspirant de Jimmy Bogard "CQRS Mythes," 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             |
+-------------+-------------------------+-----------------+

L'acceptation (par exemple, cours de validation)

La commande "Acceptation" fait principalement référence à la validation. Sans doute les résultats de la validation doit être donné de façon synchrone à l'appelant, si oui ou non la commande "réalisation" est synchrone ou en file d'attente.

Cependant, il semble que de nombreux praticiens ne pas initier de validation à partir de l'intérieur du gestionnaire de commande. De ce que j'ai vu, c'est soit parce que (1) ils ont déjà trouvé un moyen fantastique pour gérer la validation au niveau de la couche application (c'est à dire un ASP.NET MVC contrôleur de la vérification de l'état valide via les annotations de données) ou (2) une architecture est en place, qui suppose les commandes sont soumises à un (en dehors du processus de bus ou de la file d'attente. Ces dernières formes de l'asynchronie généralement n'offrent pas synchrone de validation sémantique ou des interfaces.

En bref, beaucoup de designers peut vouloir le gestionnaire de commande afin de fournir des résultats de la validation en tant que (synchrone) valeur de retour, mais ils doivent vivre avec les restrictions de l'asynchronie des outils qu'ils utilisent.

Réalisation

Concernant la "réalisation" d'une commande, le client qui a émis la commande pourriez avoir besoin de savoir le scope_identity pour un enregistrement récemment créé ou peut-être l'échec de l'information - telles que "compte à découvert."

En temps réel, il semble qu'une valeur de retour qui fait le plus de sens; les exceptions ne devraient pas être utilisés pour communiquer des affaires liées à l'insuffisance des résultats. Cependant, dans une "file d'attente" contexte...les valeurs de retour naturellement pas de sens.

C'est là que la confusion peut peut-être se résumer ainsi:

Beaucoup (la plupart?) CQRS praticiens de supposer qu'ils vont maintenant ou dans l'avenir, intégrer un asynchronisme de cadre ou de la plateforme (un bus ou une file d'attente) et donc proclamer que les gestionnaires de commande n'ont pas de valeur de retour. Cependant, certains praticiens n'ont pas l'intention d'utiliser un tel événement piloté par les constructions, et donc ils approuvent les gestionnaires de commande qui (de manière synchrone) les valeurs de retour.

Ainsi, par exemple, je crois synchrone (requête-réponse) contexte a été utilisée lors de Jimmy Bogard fourni cet exemple de l'interface de commande:

public interface ICommand<out TResult> { }

public interface ICommandHandler<in TCommand, out TResult>
    where TCommand : ICommand<TResult>
{
    TResult Handle(TCommand command);
}

Son Mediatr produit est, après tout, un outil de mémoire. Compte tenu de tout cela, je pense que la raison Jimmy soigneusement pris le temps de produire un vide de retour d'une commande n'est pas parce que "les gestionnaires de commande ne doit pas avoir de valeur de retour," mais au lieu tout simplement parce qu'il voulait que son Médiateur classe d'avoir 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 pas toutes les commandes d'avoir une véritable valeur à retourner.

Répéter et terminer

Suis-je correctement la capture pourquoi il y a une confusion sur ce sujet? Est-il quelque chose que je suis absent?

31voto

Ben Smith Points 558

En suivant les conseils de la lutte contre la Complexité dans CQRS par Vladik Khononov suggère de gestion de commande peut renvoyer des informations relatives à ses résultats.

Sans violer aucun [CQRS] principes, une commande peut revenir en toute sécurité les données suivantes:

  • Résultat de l'exécution: réussite ou d'échec;
  • Les messages d'erreur ou des erreurs de validation, en cas de défaillance;
  • Le total du nouveau numéro de version, en cas de succès;

Cette information permettra d'améliorer considérablement l'expérience utilisateur de votre système, parce que:

  • Vous n'avez pas à interroger une source externe pour l'exécution d'une commande suite, vous avez tout de suite. Il devient facile de valider les commandes, et de retourner des messages d'erreur.
  • Si vous souhaitez actualiser les données affichées, vous pouvez utiliser l'agrégat de la nouvelle version de déterminer si le modèle de vue reflète la commande en cours d'exécution ou non. Pas plus de l'affichage des données périmées.

Daniel Whittaker, qui prône le retour à une "conséquence" de l'objet à partir d'un gestionnaire de commande contenant cette information.

10voto

C-tin Gică Points 229

Eh bien, ne les gestionnaires de commande les valeurs de retour ou pas?

Ils ne devraient pas retourner Données de l'Entreprise, seuls les méta-données (concernant le succès ou l'échec de l'exécution de la commande). CQRS est CQS prises à un niveau supérieur. Même si vous voulez briser le puriste de règles et de retourner quelque chose, que feriez-vous de retour? Dans CQRS le gestionnaire de commande est une méthode d' application service qui charge l' aggregate puis appelle une méthode sur l' aggregate alors qu'il persiste aggregate. L'intention du gestionnaire de commande est de modifier l' aggregate. Vous ne savez pas ce retour qui serait indépendant de l'appelant. Chaque gestionnaire de commande de l'appelant/client veux savoir autre chose sur l'état nouveau.

Si l'exécution de la commande est le blocage (aka synchrone) puis tout ce que vous devez savoir 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 demande de l'état à l'aide d'une requête-modèle qui est le mieux adapté à vos besoins.

Penser autrement, si vous retourner quelque chose à partir d'une commande de gestionnaire, vous donner deux responsabilités: 1. modifier l'état d'agrégation et 2. requête de certains lisent-modèle.

Concernant la commande de validation, il y a au moins deux types de commande de validation:

  1. commande de contrôle de votre site, qui vérifie que la commande a les données correctes (c'est à dire une adresse e-mail est valide); cela est fait avant que la commande atteint la somme, dans le gestionnaire de commandes (demande de service) ou dans la commande constructeur;
  2. domaine invariants vérifier, qui est effectuée à l'intérieur de l'ensemble, après la commande atteint la somme (d'après une méthode est appelée sur l'ensemble) et il vérifie que le total peut muter pour le nouvel état.

Cependant, si nous allons à un certain niveau, en Presentation layer (c'est à dire un REST endpoint), le client de l' Application layer, nous pourrions retourner quoi que ce soit et nous avons l'habitude de briser les règles, parce que les points de terminaison sont conçus d'après les cas d'utilisation, vous savez exactement ce que vous voulez de retour après une commande est exécutée, dans tous les cas d'utilisation.

6voto

Misanthrope Points 199

CQRS et CQS sont comme des microservices et de la classe de décomposition: l'idée principale est la même ("tendance à petits cohérent de modules"), mais elles se situent à différents niveaux sémantiques.

Le point de CQRS est de faire de la lecture/écriture de modèles de séparation; ces détails de bas niveau comme valeur de retour de la méthode spécifique est complètement hors de propos.

Prenez avis sur la suite de Fowler citation:

Le changement que CQRS introduit est de diviser le modèle conceptuel dans des modèles distincts pour la mise à jour et l'affichage, qu'il désigne comme la Commande de Requête et de, respectivement, suivant le vocabulaire de CommandQuerySeparation.

C'est à propos des modèles, pas des méthodes.

Gestionnaire de commande peut renvoyer rien à l'exception de lire les modèles: statut (réussite/échec), les événements générés (c'est l'objectif principal de gestionnaires de commande, btw: pour générer des événements de la commande), des erreurs. Les gestionnaires de commande très souvent jeter décoché exception, il est un exemple de signaux de sortie à partir de gestionnaires de commande.

En outre, l'auteur du terme, Greg Young, dit que les commandes sont toujours synchronisés (sinon, il devient l'é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.

3voto

Misanthrope Points 199

Réponse pour @Constantin Galbenu, j'ai connu la limite.

@Misanthrope Et exactement ce que vous faites avec ces événements?

@Constantin Galbenu, dans la plupart des cas je n'ai pas besoin d'eux en tant que résultat de la commande, bien sûr. Dans certains cas, j'ai besoin d'aviser le client en réponse à cette demande d'API.

Il est extrêmement utile lorsque:

  1. Vous devez notifier sur l'erreur via des événements au lieu d'exceptions. Il se produit généralement lorsque votre modèle doit être enregistré (par exemple, il compte le nombre de tentatives avec un mauvais code/mot de passe) même si une erreur s'est passé. Aussi, certains gars ne pas utiliser les exceptions pour les affaires erreurs, seuls les é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 de jeter exceptions métier de gestionnaire de commande est OK, mais le retour évènements de domaine n'est pas.
  2. Quand l'événement se produit uniquement avec certaines circonstances, à l'intérieur de votre racine d'agrégat.

Et je peux donner des exemple pour le deuxième cas. Imaginez-nous faire de l'Amadou service, nous avons LikeStranger de commande. Cette commande PEUT entraîner StrangersWereMatched si on aime la personne qui a déjà aimé nous avant. Nous devons informer le client mobile en réponse à savoir si le match a lieu ou non. Si vous voulez juste pour vérifier matchQueryService après la commande, vous pouvez trouver des match là, mais il n'ya aucune garantie que le match a été passé, maintenant, parce que PARFOIS, Amadou montre déjà appariés étrangers (probablement, dans des zones inhabitées, peut-être incohérence, probablement vous avez juste 2ème appareil, etc.).

Vérification de la réponse si StrangersWereMatched qui s'est vraiment passé à l'instant 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 simple logique est pire? Il ne viole pas les CQRS de toute façon, je viens de faire l'implicite explicite. 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 de la correspondance n'est pas assez et vous avez besoin d'obtenir des données pour la réponse, vous devez utiliser le service de requête. Mais rien ne vous empêche de recevoir des événements à partir du gestionnaire de commande.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X