2017-03-24 2 views
1

Salut mon premier temps avec DDD/CQRS. J'ai lu plusieurs sources de connaissances et je suis encore confus un peu, peut-être quelqu'un pourrait aider :)CQRS Lire les modèles dans un NoSql (Mongo DB)

Supposons le cas simple que nous avons des produits et des clients (éventuellement différents contextes délimités). Un client peut acheter un produit et il veut voir tous les produits qu'il a achetés.

Dans ce cas, je me rends compte que je besoin d'un UserPurchasesView modèle de vue avec:

  • purchaseId (qui est une clé primaire mongo)
  • userId,
  • produit: {id, nom, son image , shortDescription, [peut-être quelques autres]}
  • prix
  • timestamp

Maintenant ... le problème est que Mon domaine génère un événement comme UserPurchasedProduct (userId, productId). Je pourrais enrichir un événement avec un prix, un nom de produit ou peut-être quelque chose d'autre mais pas tous les champs. J'arrive à un point où enrichir semble être faux.

En ce moment je me rends compte que je besoin de quelque chose comme ProductDetailsView:

  • productId (clé primaire)
  • prix
  • Nom
  • shortDescription
  • logo

Ce point de vue est maintenu par des événements tels que: ProductCreated, ProductRenamed, ProductImageChanged

Et maintenant nous avons 2 options ...

  1. Regardez dans l'ProductDetailsView lorsque l'événement UserPurchasedProduct arrive, prendre tous les détails du produit nécessaires et enregistrez dans UserPurchasesView pour des lectures plus rapides. Cette solution ne semble pas si mauvaise mais elle introduit un peu de couplage supplémentaire et il me semble que ces vues ne peuvent pas être bien mises à l'échelle en cas de besoin. Les deux vues doivent également être reconstruites ensemble lors de la réponse à tous les événements du magasin d'événements (la reconstruction est également plus complexe dans ce cas).
  2. Conserver uniquement le productId dans UserPurchasesView et lire plusieurs vues lorsque l'utilisateur interroge ses achats. C'est un traitement supplémentaire qui devrait être fait quelque part. Dans le frontend, dans le contrôleur backend ou dans certaines API de haut niveau de modèle de lecture. MISE À JOUR: Je me suis également rendu compte que je devrais garder au moins le prix et peut-être le nom du produit dans UserPurchasesView (au cas où il changerait) mais parfois vous avez besoin de la valeur du moment de l'achat et parfois vous avez besoin de la valeur récente . Le scénario dépend d'une entreprise mais nous pouvons imaginer les deux.

Aucune de ces solutions ne me semble parfaite. Est-ce que j'ai tort, est-ce que je manque quelque chose ou est-ce juste la manière de le faire? Merci!

Répondre

2

Vous comprenez bien.

Vous devez donc choisir entre le couplage entre les modèles lues et le couplage entre l'interface utilisateur et les modèles de lecture individuels.

L'un des principaux avantages de CQRS/ES est la possibilité de créer des modèles de lecture rapide flamboyants (vues si vous le souhaitez), sans aucune jointure, le cache parfait comme je l'ai vu appelé. J'ai personnellement choisi à chaque fois la première approche, avec dénormalisation complète des données. Les vues sont très rapides et les modèles très propres et clairs. Ceci est la solution parfaite si vous voulez optimiser le côté lecture de votre application (et je pense que vous devriez). En écoutant les bons événements, vous pouvez synchroniser ces modèles de lecture avec le reste de l'application.

1

Il y a une option 3:

La projection responsable de la vue UserPurchasesView écoute non seulement UserPurchasedProduct événements, mais aussi à ProductCreated, ProductRenamed, ProductImageChanged - tous les événements liés aux produits qui affectent la UserPurchasesView. Maintenant, ainsi que la collection UserPurchasesView pour le modèle de lecture dont elle est responsable, elle a également besoin d'une collection privée pour gérer les bits des produits qui l'intéressent: ({id, name, image, shortDescription, [peut-être quelques autres] }), de sorte que lorsqu'un nouvel événement d'achat arrive, vous avez un endroit pour obtenir l'état initial de ces champs de produits. Étant donné que UserPurchasesView a besoin d'écouter certains de ces événements de produits afin de rester à jour quand un produit change, ce n'est pas vraiment beaucoup de travail supplémentaire, et évite toute dépendance sur une autre projection (ProductDetailsView). La dépendance de la projection croisée présente également un problème potentiel dû à la cohérence éventuelle: que se passe-t-il si le produit n'apparaît pas encore dans la vue des détails du produit lors de la réception de l'événement UserPurchasedProduct?

Pour éviter les problèmes de simultanéité, il est plus simple de faire en sorte que chaque projection ne soit gérée que par un seul processus et un seul thread. Ainsi, tant que la projection peut recevoir des événements dans l'ordre d'un flux à l'autre (afin que la création du produit soit garantie avant l'achat du produit), vous n'aurez aucun problème à voir un achat avant que le produit existe. Si vous introduisez un sharding ou tout autre multi-threading dans votre projection, cela devient plus compliqué.

+0

En fait, c'était ce que je pensais. Dans ma solution, j'ai une «projection» à l'écoute des événements d'achat et de produits. Mais cette projection de processus unique maintient 2 collections mongo - UserPurchases and Products. Actuellement, il gère également plus de collections pour différentes vues d'interface utilisateur, mais cela peut être facilement divisé en plusieurs projections lorsque cela est nécessaire. Néanmoins, ces 2 collections doivent toujours être proches et maintenues ensemble (également lors de la reconstruction de la projection avec tous les événements du magasin) – Mark

+0

* Fondamentalement * c'est la première option mais avec plus de détails ajoutés. –