Architecture-center: Quelle est la différence entre ProductsCommandHandler (CQRS) et ProductRepository (arc traditionnel)?

Créé le 15 nov. 2019  ·  5Commentaires  ·  Source: MicrosoftDocs/architecture-center

Salut,
Merci d'avoir écrit cet article sur le CQRS. J'essaie de comprendre à quoi ressemble la mise en œuvre et je passais en revue les exemples. L'exemple de code de référentiel fourni dans l'exemple ressemble beaucoup à la façon dont on écrirait normalement un dépôt, à l'exception de la convention de dénomination. Par exemple, je pense que le référentiel donné ci-dessous est le même que ProductsCommandHandler et probablement une différence serait qu'il n'y a pas de GetProduct ici que nous ajouterions normalement. Pourriez-vous s'il vous plaît expliquer ce que je n'obtiens pas ici?

public class ProductRepository {
  void AddNewProduct(Product newProduct) {
    ...
  }
  void RateProduct(int productId, int userId, int rating) {
    var product = repository.Find(productId);
    if (product != null)
    {
      product.RateProduct(userId, rating);
      repository.Save(product);
    }
  }
}

Détails du document

Ne modifiez pas cette section.

Pri1 architecture-centesvc assigned-to-author cloud-fundamentalsubsvc product-question triaged

Commentaire le plus utile

@martinmthomas En fait, le gestionnaire de commandes n'est pas un référentiel. Il utilise un référentiel.

Le gestionnaire de commandes est censé "traiter les commandes réelles, si cela est approprié". Dans l'exemple, la classe ProductsCommandHandler reçoit un IRepository<Product> dans son constructeur.

Supposons que l'utilisateur attribue au produit 5555 une note de 4 étoiles.

  • L'utilisateur voit une interface. Disons une page Web (peut être un programme local .exe ou autre, imaginons un site Web).
  • L'utilisateur clique sur un bouton. Disons que cela déclenche un AJAX POST vers une route / un tarif avec des données {"product":"5555","stars":4}
  • La route POST a un contrôleur. Ce contrôleur est associé à une opération "écriture" car il provient d'un appel POST.
  • Le contrôleur ici, dans une approche classique, chargerait simplement le référentiel de produits classique et chargerait le produit 5555. Ensuite, dites au produit "vous êtes maintenant classé 4 étoiles" et enregistrez.
  • Dans cette approche, le contrôleur crée la commande RateProduct et remplit le produit 5555, étoiles 4. Dans l'exemple, il remplit également qui évalue.
  • A ce moment, le contrôleur "envoie la commande" au modèle d'écriture. Vous avez 2 options ici: appeler un gestionnaire de commandes ou le mettre en file d'attente.
  • Dites que vous n'avez pas de file d'attente. Vous obtenez alors le CommandHandler dans votre contrôleur (probablement par injection de dépendance) et placez simplement la commande là: h.Handle (c); où c est la commande RateProduct.
  • Disons que vous avez une file d'attente. Vous obtenez alors la file d'attente dans votre contrôleur (probablement par injection de dépendance) et mettez simplement la commande en file d'attente là-bas: q.Queue (c);
  • Dans ce dernier cas, l'écouteur de la file d'attente sortira la commande de la file d'attente et appellera le gestionnaire de commandes à la place.
  • En tant que troisième option (recommandée), votre contrôleur ne choisit pas si la commande est traitée de manière synchrone (récupérez le gestionnaire) ou asynchrone (récupérez la file d'attente) mais devrait utiliser une "approche générique" qui consiste à utiliser un bus de commande où les commandes sont placés. Disons que le bus de commande est b, alors vous feriez b.Send (c); où c est la commande RateProduct.
  • Avec cette 3ème approche, vous placez le gestionnaire de commandes à la fin du bus, et vous pouvez configurer les middlewares pour "retirer la commande du bus et la placer dans la file d'attente".

Donc 3 options: a) Récupérez le gestionnaire, b) Récupérez la file d'attente, c) Obtenez un bus et laissez-le décider d'envoyer la commande au gestionnaire ou à la file d'attente.

Quelle que soit l'approche que vous faites ... la principale différence avec "un référentiel classique" est que vous n'invoquez JAMAIS le référentiel lui-même dans le contrôleur. Le contrôleur ne "sait pas comment gérer les entités" (référentiel classique) mais sait "comment gérer les INTENTS OF FAIRE CHOSES aux entités" (gestionnaire de commandes).

Ensuite, c'est le gestionnaire de commandes qui -comme son nom l'indique- il "gère la commande" qui vient de quelque part (un contrôleur web html, un contrôleur API, une ligne de commande, peu importe) qui a été envoyé comme une intention et c'est le CommandHandler (qui appartient au côté écriture) qui décide quoi faire avec cette commande.

Par exemple, le CommandHandler peut utiliser un référentiel pour obtenir le produit, définir l'état et enregistrer (comme dans l'exemple), mais il peut également écrire dans un journal des événements, déclencher des éléments pour mettre à jour les côtés de lecture, déclencher des connecteurs externes ou autre.

L'interface utilisateur basée sur les tâches de l'utilisateur pour évaluer le produit et son contrôleur comme étant AGNOSTIQUE de «l'endroit» où le système de mise en vedette / d'évaluation est placé ou stocké. Imaginez que vous concevez un système à partir de zéro et que la classe Product contient déjà la méthode RateProduct () comme dans l'exemple. Bien.

Mais ... que se passe-t-il si vous avez un ancien système avec une approche «ancienne» du produit. Dans le «modèle» (c'est-à-dire: Business Mind), il n'y a pas de «note» du produit. Au lieu de cela, le responsable du marketing veut «ajouter» une note au produit existant, mais toute l'entreprise convient qu'il s'agit d'une «chose externe». Souhaitez-vous utiliser le référentiel de produits? Ou peut-être un autre stockage d'aide pour ne pas «toucher» le produit et le référentiel de produits déjà en cours de fonctionnement et entièrement testés?

Si vous utilisez des commandes, cela ne dérange pas le contrôleur d'écriture. Le web, l'API et la CLI enverront tous simplement la "commande" au gestionnaire de commandes (soit directement, via la file d'attente ou via un bus de commande qui acheminera à son tour vers le gestionnaire ou vers une file d'attente) et ils oublient. Ensuite, le gestionnaire de commandes décidera quoi faire avec le "RateProductCommand" dans un point centralisé et petit de votre code source, ce découplant la façon dont cela est géré du code de l'application, gagnant ainsi en maintenabilité.

Ensuite, le gestionnaire décidera s'il est approprié d'utiliser le ProductRepository ou toute autre approche pour stocker "l'évaluation d'un produit".

Alors, pour répondre:

CommandHandler => n'a rien à voir avec les droits. Il gère les «intentions de l'utilisateur» (ce qui pourrait finalement être de modifier des entités; donc très probablement le gestionnaire de commandes utilise un référentiel).
Repository => le stockage réel pour une certaine entité.

J'espère aider.
Xavi.

Tous les 5 commentaires

@martinmthomas Merci pour votre question! Nous examinerons et fournirons une mise à jour le cas échéant.

@MikeWasson des pensées ici?

AB # 160217 - Merci d'avoir signalé ce problème - ce problème est en cours d'examen

@martinmthomas En fait, le gestionnaire de commandes n'est pas un référentiel. Il utilise un référentiel.

Le gestionnaire de commandes est censé "traiter les commandes réelles, si cela est approprié". Dans l'exemple, la classe ProductsCommandHandler reçoit un IRepository<Product> dans son constructeur.

Supposons que l'utilisateur attribue au produit 5555 une note de 4 étoiles.

  • L'utilisateur voit une interface. Disons une page Web (peut être un programme local .exe ou autre, imaginons un site Web).
  • L'utilisateur clique sur un bouton. Disons que cela déclenche un AJAX POST vers une route / un tarif avec des données {"product":"5555","stars":4}
  • La route POST a un contrôleur. Ce contrôleur est associé à une opération "écriture" car il provient d'un appel POST.
  • Le contrôleur ici, dans une approche classique, chargerait simplement le référentiel de produits classique et chargerait le produit 5555. Ensuite, dites au produit "vous êtes maintenant classé 4 étoiles" et enregistrez.
  • Dans cette approche, le contrôleur crée la commande RateProduct et remplit le produit 5555, étoiles 4. Dans l'exemple, il remplit également qui évalue.
  • A ce moment, le contrôleur "envoie la commande" au modèle d'écriture. Vous avez 2 options ici: appeler un gestionnaire de commandes ou le mettre en file d'attente.
  • Dites que vous n'avez pas de file d'attente. Vous obtenez alors le CommandHandler dans votre contrôleur (probablement par injection de dépendance) et placez simplement la commande là: h.Handle (c); où c est la commande RateProduct.
  • Disons que vous avez une file d'attente. Vous obtenez alors la file d'attente dans votre contrôleur (probablement par injection de dépendance) et mettez simplement la commande en file d'attente là-bas: q.Queue (c);
  • Dans ce dernier cas, l'écouteur de la file d'attente sortira la commande de la file d'attente et appellera le gestionnaire de commandes à la place.
  • En tant que troisième option (recommandée), votre contrôleur ne choisit pas si la commande est traitée de manière synchrone (récupérez le gestionnaire) ou asynchrone (récupérez la file d'attente) mais devrait utiliser une "approche générique" qui consiste à utiliser un bus de commande où les commandes sont placés. Disons que le bus de commande est b, alors vous feriez b.Send (c); où c est la commande RateProduct.
  • Avec cette 3ème approche, vous placez le gestionnaire de commandes à la fin du bus, et vous pouvez configurer les middlewares pour "retirer la commande du bus et la placer dans la file d'attente".

Donc 3 options: a) Récupérez le gestionnaire, b) Récupérez la file d'attente, c) Obtenez un bus et laissez-le décider d'envoyer la commande au gestionnaire ou à la file d'attente.

Quelle que soit l'approche que vous faites ... la principale différence avec "un référentiel classique" est que vous n'invoquez JAMAIS le référentiel lui-même dans le contrôleur. Le contrôleur ne "sait pas comment gérer les entités" (référentiel classique) mais sait "comment gérer les INTENTS OF FAIRE CHOSES aux entités" (gestionnaire de commandes).

Ensuite, c'est le gestionnaire de commandes qui -comme son nom l'indique- il "gère la commande" qui vient de quelque part (un contrôleur web html, un contrôleur API, une ligne de commande, peu importe) qui a été envoyé comme une intention et c'est le CommandHandler (qui appartient au côté écriture) qui décide quoi faire avec cette commande.

Par exemple, le CommandHandler peut utiliser un référentiel pour obtenir le produit, définir l'état et enregistrer (comme dans l'exemple), mais il peut également écrire dans un journal des événements, déclencher des éléments pour mettre à jour les côtés de lecture, déclencher des connecteurs externes ou autre.

L'interface utilisateur basée sur les tâches de l'utilisateur pour évaluer le produit et son contrôleur comme étant AGNOSTIQUE de «l'endroit» où le système de mise en vedette / d'évaluation est placé ou stocké. Imaginez que vous concevez un système à partir de zéro et que la classe Product contient déjà la méthode RateProduct () comme dans l'exemple. Bien.

Mais ... que se passe-t-il si vous avez un ancien système avec une approche «ancienne» du produit. Dans le «modèle» (c'est-à-dire: Business Mind), il n'y a pas de «note» du produit. Au lieu de cela, le responsable du marketing veut «ajouter» une note au produit existant, mais toute l'entreprise convient qu'il s'agit d'une «chose externe». Souhaitez-vous utiliser le référentiel de produits? Ou peut-être un autre stockage d'aide pour ne pas «toucher» le produit et le référentiel de produits déjà en cours de fonctionnement et entièrement testés?

Si vous utilisez des commandes, cela ne dérange pas le contrôleur d'écriture. Le web, l'API et la CLI enverront tous simplement la "commande" au gestionnaire de commandes (soit directement, via la file d'attente ou via un bus de commande qui acheminera à son tour vers le gestionnaire ou vers une file d'attente) et ils oublient. Ensuite, le gestionnaire de commandes décidera quoi faire avec le "RateProductCommand" dans un point centralisé et petit de votre code source, ce découplant la façon dont cela est géré du code de l'application, gagnant ainsi en maintenabilité.

Ensuite, le gestionnaire décidera s'il est approprié d'utiliser le ProductRepository ou toute autre approche pour stocker "l'évaluation d'un produit".

Alors, pour répondre:

CommandHandler => n'a rien à voir avec les droits. Il gère les «intentions de l'utilisateur» (ce qui pourrait finalement être de modifier des entités; donc très probablement le gestionnaire de commandes utilise un référentiel).
Repository => le stockage réel pour une certaine entité.

J'espère aider.
Xavi.

Comme @xmontero l'a mentionné, ProductsCommandHandler et ProductRepository n'ont qu'une relation «a». ProductsCommandHandler agit comme interface entre ProductApi / Controller et ProductRepository. Cette implémentation permet de séparer les commandes des requêtes. Dans l'approche traditionnelle, nous utilisons directement le référentiel du contrôleur en maintenant l'exécution des commandes et des requêtes ensemble, donc pas besoin d'une autre interface entre les deux.

Vous avez appliqué le modèle de conception CQRS dans votre application, si vous gardez les commandes séparées des requêtes. Cela vous amène à profiter de tous les avantages du CQRS mentionnés dans les articles.

Cette page vous a été utile?
0 / 5 - 0 notes