Conception pilotée par domaine et interaction entre domaines

10

Je suis un débutant DDD relatif, mais je lis tout et tout ce que je peux mettre la main sur pour résumer et distiller mes connaissances.

Je suis tombé sur cette question DDD, et l'une des réponses m'a intrigué.

Contextes et domaines DDD délimités?

Dans l'une des réponses, l'affiche donne l'exemple d'un système de commerce électronique avec des produits dans au moins 2 domaines:

1) Catalogue de produits 2) Gestion des stocks

OK, tout cela a du sens, c'est-à-dire que dans votre frontal de commerce électronique, vous êtes intéressé à afficher les informations sur le produit, et pas à la gestion des stocks.

MAIS. Vous voudrez peut-être afficher le niveau d'inventaire sur la page Web, ou vous pouvez afficher le numéro d'édition de l'inventaire en stock (imaginez que votre inventaire est des livres, des magazines, etc.). Ces informations proviennent du domaine d'inventaire.

Alors, comment géreriez-vous cela? Voudriez-vous

a) Charger à la fois le domaine du produit et les agrégats du domaine d'inventaire? b) Souhaitez-vous conserver certaines propriétés sur votre entité de domaine de produit pour le nombre en stock et l'édition en stock, puis utiliser les événements de domaine pour les mettre à jour lorsque l'entité d'inventaire est mise à jour?

Une dernière question. Je sais que nous sommes censés oublier / ignorer la persistance du domaine et simplement penser au domaine. Mais juste pour y réfléchir, dans l'exemple ci-dessus, nous nous retrouverions avec potentiellement 2 tables DB pour le catalogue de produits et l'inventaire des produits. Maintenant, utilisons-nous le même identifiant dans ces derniers car c'est le même produit. Ou, pourrions-nous utiliser 1 table et 1 ligne de table pour les données et simplement mapper les données pertinentes sur les propriétés d'agrégation?

PendorPaul
la source

Réponses:

8

Vous voudrez peut-être afficher le niveau d'inventaire sur la page Web, ou vous pouvez afficher le numéro d'édition de l'inventaire en stock (imaginez que votre inventaire est des livres, des magazines, etc.). Ces informations proviennent du domaine d'inventaire.

La principale chose à noter à ce stade est que vous parlez d'une vue, c'est-à-dire que l'utilisation de données périmées est acceptable.

Cela étant dit, vous n'avez pas besoin d'interagir avec les agrégats (qui sont responsables d'empêcher les modifications de violer l'invariant métier), mais avec une représentation d'une copie récente de l'état de l'agrégat.

Donc, ce que j'attendrais normalement, c'est une requête exécutée sur le catalogue de produits et une autre sur l'inventaire, et quelque chose pour composer les deux dans le DTO dont vous avez besoin pour prendre en charge la vue.

Charger à la fois le domaine du produit et les agrégats du domaine d'inventaire?

C'est donc proche . Nous n'avons pas besoin de charger les agrégats, car nous n'allons rien changer. Mais nous avons besoin de leur état; afin que nous puissions charger cela. Cela dit, je m'attendrais normalement à ce que les deux domaines s'exécutent dans des processus différents. Par conséquent, nous appellerions les deux, pas les chargerons tous les deux.

Souhaitez-vous conserver certaines propriétés sur votre entité de domaine de produit pour le nombre en stock et l'édition en stock, puis utiliser les événements de domaine pour les mettre à jour lorsque l'entité d'inventaire est mise à jour?

"Ne traversez pas les ruisseaux. Ce serait mauvais."

Utiliser des événements pour coordonner les informations entre les contextes de domaine: excellente idée. Pousser des concepts qui appartiennent à un domaine dans un autre: à l'opposé d'une grande idée, sauf plus.

Vous voulez garder les domaines propres. Les applications qui interagissent avec les domaines, ce n'est pas si important. Ainsi, par exemple, il est raisonnable que l'application d'inventaire appelle un service dans l'application de produit pour interroger certains concepts spécifiques au produit à ajouter à une vue. Ou vice versa.

Je ne connais aucune raison pour laquelle une seule application doit être limitée à un seul domaine. Tant qu'il n'y a qu'une seule source de vérité, vous pouvez répartir les transactions comme bon vous semble.

Mais juste pour y réfléchir, dans l'exemple ci-dessus, nous nous retrouverions avec potentiellement 2 tables DB pour le catalogue de produits et l'inventaire des produits. Maintenant, utilisons-nous le même identifiant dans ces derniers car c'est le même produit.

Ce serait la manière la plus simple. En termes plus larges, vous utilisez le même identifiant car l'entité réelle est la même; les deux contextes bornés différents modélisent cette entité différemment, mais le modèle n'est pas l'entité du monde réel.

Lorsque cela ne fonctionne pas, vous aurez besoin d'une requête à utiliser pour combler l'écart. Je pense que la variation la plus courante de cela est que l'entité plus récente préserve l'id de l'entité plus ancienne. Vous le verrez également dans un seul pays de la Colombie-Britannique: les candidats, lorsqu'ils sont approuvés, deviennent des clients. Il s'agit d'un agrégat différent (l'état associé à un client est soumis à un invariant différent de celui du demandeur); donc si votre couche de persistance utilise des flux d'événements, le flux du nouvel agrégat aura besoin d'un identifiant différent. Il y aura donc un peu d'état quelque part qui dit "ce demandeur est devenu ce client".

Ou, pourrions-nous utiliser 1 table et 1 ligne de table pour les données et simplement mapper les données pertinentes sur les propriétés d'agrégation?

OUI! Non, ne fais pas ça. Vous ajoutez un conflit d'opération sans aucune raison commerciale de le faire.

VoiceOfUnreason
la source
J'ai coché cela comme réponse, crédit également à @guillaume ci-dessous qui a également souligné que la lecture des données à afficher dans une vue ne nécessite pas de charger les agrégats. Merci pour cette longue réponse détaillée. Issu de la première approche du modèle de données "traditionnel", j'ai eu du mal à me forcer à oublier la couche de persistance et à me concentrer sur le langage du domaine. Juste au moment où je pense l'avoir compris, j'ai lu un autre article qui fait exploser ma compréhension.
PendorPaul
Je viens de lire cet article msdn.microsoft.com/en-us/magazine/dn802601.aspx qui détaille l'utilisation des événements de domaine pour dupliquer certaines données entre les contextes. L'exemple étant le partage d'une liste de clients entre le service client et un système de traitement des commandes. Cela consiste à dupliquer les données client dans le système de commande et à utiliser des événements pour synchroniser les données. Cela va à l'encontre de ce que nous avons répondu ici. Certes, dans l'exemple lié, le contexte de la commande a juste besoin d'un identifiant client, qui peut être rempli à partir de l'application qui a accès au contexte du service client.
PendorPaul
Vous voudrez être très précis dans votre réflexion, ici. Copier des données entre des systèmes n'est PAS la même chose que copier des données entre des modèles de domaine. Chaque fois que vous voyez "lecture seule [marmonner]", c'est un gros indice que les données n'appartiennent pas à ce domaine (même si l'application peut encore s'en soucier).
VoiceOfUnreason
Oui, c'est ce que je pensais aussi. Le changement du processus de réflexion est déjà assez difficile sans que des «experts» publient des articles qui bouent les eaux. J'ai passé aujourd'hui à analyser vraiment mon domaine, pour essayer de vraiment comprendre où se trouvent mes racines BC et agrégées. J'y suis à peu près, mais je sais aussi que je peux affiner et refactoriser mon modèle au fur et à mesure. Je suis coincé dans l'enfer de l'analyse depuis des jours maintenant, j'ai peur de commencer à écrire du code, craignant de ne pas avoir DDD directement dans la tête. Je pense qu'il est préférable de continuer et de remettre en question mon code et de savoir si le modèle est bon. Je vais y arriver. Merci
PendorPaul
J'ai peut-être mal lu, mais la pratique à laquelle je pense que vous parlez s'appelle Event-Carried State Transfer (Event-Carried State Transfer) et il peut s'agir d'un modèle très puissant, en particulier pour les vues de tableau de bord / tableau où vous devez filtrer et paginer les données. à travers les services. Vous pouvez créer des "projections" (essentiellement des vues) à partir d'événements de domaine pour piloter ces tableaux de bord. Je l'ai déjà utilisé avec succès. Cela peut être un outil très puissant dans le bon scénario. À mon humble avis, le plus important ici est que vous réalisez que ces projections ne représentent pas des modèles de domaine et qu'elles ne doivent pas contenir de logique métier.
Jordan
3

Je pense que votre question appelle vraiment 2 ensembles d'options orthogonales -

  • Chargez-vous deux objets et présentez-vous leurs données ensemble ou chargez-vous 1 objet contenant tout ce que vous voulez?

  • Utilisez-vous des agrégats pour afficher des éléments, ou autre chose?

Si vous croyez en l'approche CQRS, il s'avère que les agrégats peuvent ne pas être le meilleur pari pour les lectures. Chaque fois que vous chargez un agrégat, que ce soit pour afficher ses données ou les modifier, vous ajoutez de la concurrence et des conflits à votre système. De plus, les agrégats sont potentiellement plus volumineux et plus lents à charger que si vous utilisez des modèles de lecture ad hoc adaptés à l'affichage.

La solution a) de votre Q semble sujette à beaucoup de ces pièges. L'option b) peut être valide, mais je ne l'utiliserais que si des données du InventoryManagementBC sont nécessaires pour appliquer des invariants lors de la mutation de l' Productagrégat. Il est préférable qu'un agrégat contienne toutes les données nécessaires pour vérifier ses règles métier lors de la modification, mais du côté lecture, il peut s'asseoir n'importe où.

En ce qui concerne les données, une recommandation courante est de donner aux contextes délimités leur propre base de données (pour des raisons de déployabilité et de SoC). Vous devrez probablement utiliser les mêmes identifiants si vous souhaitez faire correspondre les produits entre les deux BC.

À propos des interactions entre BC, vous pouvez également consulter /programming/16713041/communicating-between-two-bounded-contexts-in-ddd

guillaume31
la source
1
OK, ça aide. Essentiellement, nous utilisons Aggregate Roots et BC pour nous assurer que nos invariants sont cohérents et que nos données sont valides et que les opérations que nous voulons effectuer sont regroupées dans notre agrégat ou BC. Quand il s'agit de lire et d'afficher ces données, nous pouvons gérer cela séparément et léger sans que tous les agrégats / BC soient hydratés. Après tout, pourquoi devons-nous charger l'agrégat alors que tout ce que nous faisons est d'afficher les données dans un rapport ou à l'écran. Cela est parfaitement logique. Merci.
PendorPaul
C'est là que le CQRS brille vraiment vraiment. Vous pouvez simplement effectuer une requête SQL, joindre les tables et renvoyer la requête personnalisée pour une vue spécifique. Il efface également votre dépôt du désordre de la méthode de requête. En outre, vous pouvez même répliquer des données sur un service comme ElasticSearch et interroger celui-ci.
doesnotmatter
1

DDD est destiné aux applications où la logique métier est complexe. «imprimer quelque chose» n'est pas une logique métier complexe. Ce n'est en fait pas du tout une logique métier.

Si, la logique métier dans un contexte a besoin d'informations pour gérer correctement certains cas d'utilisation, alors ces informations font partie de ce contexte. Ainsi, l'idée qu'un contexte borné puisse avoir besoin d'informations disponibles dans différents contextes bornés n'a pas de sens, car le contexte borné contient toutes les informations dont il a besoin.

Euphorique
la source
OK, alors prenez quelque chose comme Amazon, c'est un système complexe avec une logique métier complexe. Ils ont la gestion des catalogues et la gestion des stocks. Ils n'ont pas besoin de totaux d'inventaire pour gérer le catalogue, j'entends par là le nom du produit, la description, le type, l'état, etc. Cependant, ils indiquent le nombre d'articles en stock sur la page d'accueil de leur magasin. Dans ce scénario, où vous souhaitez séparer vos domaines de gestion de catalogue de produits et d'inventaire de produits, mais vous devez afficher des informations sur l'inventaire sur votre page de produit, comment procédez-vous?
PendorPaul
Je suppose que ce que je dis, c'est que le domaine du catalogue de produits possède toutes les informations dont il a besoin pour gérer le catalogue de produits. Le domaine d'inventaire possède toutes les informations dont il a besoin pour gérer l'inventaire. Cependant, lorsque je veux afficher des informations à un utilisateur et que les données proviennent de 2 domaines, où dois-je faire? Dois-je simplement charger les deux domaines dans mon interface utilisateur et lier les propriétés que je veux afficher? Ou ai-je un mécanisme de rapport / lecture où je retourne un type anonyme ou un DTO contenant les données dont j'ai besoin pour mon interface utilisateur?
PendorPaul
Il est comique de revenir sur mes anciens commentaires il y a 11 mois, mais cela semble être une vie.Vous aviez complètement euphorique sur la lecture et l'écriture. L'application sur laquelle je travaille a beaucoup évolué au fur et à mesure de ma compréhension. J'utilise maintenant les méthodes CQRS, via Jimmy Bogards Mediatr. La grande flexibilité d'avoir des commandes qui interagissent avec les agrégats, mais ensuite d'utiliser des requêtes et des gestionnaires de requêtes pour ramener tout ce dont j'ai besoin pour l'affichage est incroyable. Enveloppez cela dans des vues qui appellent ces QueryHandlers, et le découplage est bon avec celui-ci. Merci
PendorPaul
@PendorPaul, je pense que je suis là où vous étiez il y a 11 (maintenant 13) mois. Qu'est-ce qui vous a amené là où vous êtes maintenant?
Greg Bell
1
@GregBell Vous devez forcer un changement de mentalité. J'étais coincé dans l'approche de "concevoir une base de données, construire un niveau de données, construire une logique métier ... etc". Et j'étais concentré sur la création de toutes les entités englobantes. c'est-à-dire un "produit" dans un site de commerce électronique, qui gérait tout, de la tarification, la description, l'inventaire, l'emplacement des stocks ... mais cela devient extrêmement complexe. L'approche du contexte borné signifie que dans le contexte de l'inventaire, le «produit» ne contient que des informations et un comportement pour la gestion des stocks. La description et les images sont gérées dans un contexte de contenu, la tarification dans le contexte de tarification.
PendorPaul
1

De mon point de vue, il existe différentes définitions de «Produit» - chaque contexte de délimitation a sa propre définition du «produit» - domaine:

  • Dans le contexte Content-Management-Bounding, un produit a une image et un texte de description.
  • Dans le contexte de l'inventaire, un produit a des quantités en stock, le vendeur du produit, des prévisions lorsque le produit sera disponible
  • Dans le contexte de la limitation des prix et de la concentration des prix, il existe des règles sur le coût d'un produit par quantité.

En plus de cela, j'ajouterais un contexte de magasinage supplémentaire avec sa propre définition de produit (une combinaison pertinente des domaines de produits des autres contextes de délimitation).

Un produit de boutique aurait «une image et un texte de description» du contenu et de la disponibilité de «l'inventaire» mais pas du «vendeur de produits» de l'inventaire.

Ce contexte de magasinage supplémentaire dépend du contenu, du stock et du prix du contexte de magasinage.

k3b
la source
Alors, comment créez-vous ce Shop-Product BC? Dans ce contexte, avez-vous une référence au produit et à l'inventaire BC, et hydratez-vous ceux de votre magasin de persistance lorsque vous chargez un magasin-produit BC, puis offrez-vous les propriétés que vous voulez de ces BC via vos propriétés StoreProduct? J'ai trouvé cet article qui va dans le sens de ce que je pensais, traverser les événements de la Colombie-Britannique, mais @ guillaume13 ci-dessus a souligné qu'à des fins d'affichage, je peux éviter le BC et simplement retirer les données dont j'ai besoin pour ma vue. msdn.microsoft.com/en-us/magazine/dn802601.aspx
PendorPaul