DDD Aggregates est-il vraiment une bonne idée dans une application Web?

40

Je me plonge dans Domain Driven Design et certains des concepts que je découvre ont beaucoup de sens en surface, mais lorsque j'y réfléchis davantage, je me demande si c'est vraiment une bonne idée.

Le concept d'agrégats, par exemple, a du sens. Vous créez de petits domaines de propriété afin que vous n'ayez pas à traiter avec le modèle de domaine entier.

Toutefois, lorsque j'y pense dans le contexte d'une application Web, nous consultons fréquemment la base de données pour récupérer de petits sous-ensembles de données. Par exemple, une page peut ne lister que le nombre de commandes, avec des liens sur lesquels cliquer pour ouvrir la commande et voir son identifiant.

Si je ne me trompe pas Agrégats compréhension, je généralement utiliser le modèle référentiel pour retourner un OrderAggregate qui contiendrait les membres GetAll, GetByID, Deleteet Save. Ok, ça sonne bien. Mais...

Si j'appelle GetAll pour répertorier toutes mes commandes, il me semblerait que ce schéma exigerait le renvoi de la liste complète des informations globales, des commandes complètes, des lignes de commande, etc. Lorsque je n'ai besoin que d'un petit sous-ensemble de ces informations (informations d'en-tête seulement).

Est-ce que je manque quelque chose? Ou y a-t-il un niveau d'optimisation que vous utiliseriez ici? Je ne peux pas imaginer que quiconque puisse préconiser de renvoyer des agrégats complets lorsque vous n'en avez pas besoin.

Certes, vous pouvez créer des méthodes sur votre référentiel GetOrderHeaders, mais cela semble aller à l’encontre du but qui consiste à utiliser un modèle tel que référentiel.

Quelqu'un peut-il clarifier cela pour moi?

MODIFIER:

Après de nombreuses recherches, je pense que le point négatif est qu'un modèle de référentiel pur diffère de ce que la plupart des gens pensent d'un référentiel.

Fowler définit un référentiel comme un magasin de données qui utilise la sémantique de la collection et est généralement conservé en mémoire. Cela signifie créer un graphe d'objet entier.

Evans modifie le référentiel pour inclure les racines d'agrégat. Le référentiel est donc amputé pour ne prendre en charge que les objets d'un agrégat.

La plupart des gens semblent penser que les référentiels sont des objets d'accès aux données glorifiés, dans lesquels vous créez simplement des méthodes pour obtenir toutes les données que vous souhaitez. Cela ne semble pas être l'intention telle que décrite dans Patterns of Enterprise Application Architecture de Fowler.

D'autres encore considèrent un référentiel comme une simple abstraction utilisée principalement pour faciliter les tests et les moqueries, ou pour dissocier la persistance du reste du système.

Je suppose que la réponse est qu'il s'agit d'un concept beaucoup plus complexe que je ne le pensais au départ.

Erik Funkenbusch
la source
4
"Je suppose que la réponse est qu'il s'agit d'un concept beaucoup plus complexe que ce que je pensais au départ." C'est très vrai.
Quentin-Starin
dans votre cas, vous pouvez créer un proxy pour l’objet racine agrégé qui récupère et met en cache les données uniquement lorsque cela est demandé
Steven A. Lowe
Ma suggestion est d'implémenter la charge paresseuse dans les associations d'agrégat racine. Ainsi, vous pouvez récupérer une liste de racines sans charger trop d'objets.
Juliano
4
presque 6 ans plus tard, toujours une bonne question. Après avoir lu le chapitre du livre rouge, je dirais: ne faites pas vos agrégats trop gros. Il est tentant de choisir un concept de niveau supérieur dans votre domaine et de le déclarer comme étant la racine de leur règle, à l'exception de DDD, qui préconise des agrégats plus petits. Et atténue les inefficacités telles que celles décrites ci-dessus.
Cpt. Senkfuss
Vos agrégats doivent être aussi petits que possible tout en étant naturels et efficaces pour le domaine (un défi!). En outre, il est tout à fait souhaitable et souhaitable que vos pensions disposent de méthodes très spécifiques .
Timo

Réponses:

30

N'utilisez pas votre modèle de domaine et vos agrégats pour l'interrogation.

En fait, ce que vous demandez est une question assez commune pour qu'un ensemble de principes et de modèles ait été établi pour éviter cela. Cela s'appelle CQRS .

Quentin Starin
la source
2
@Mystere Man: Non, c'est de fournir les données minimales nécessaires. C'est l'un des objectifs majeurs d'un modèle de lecture séparé. Cela aide également à résoudre certains problèmes de concurrence dès le départ. CQRS présente plusieurs avantages lorsqu'il est appliqué à DDD.
Quentin-Starin
2
@Mystere: Je suis assez surpris que cela vous échappe si vous lisez l'article que j'ai lié à. Reportez-vous à la section intitulée "Requêtes (génération de rapports)": "Lorsqu'une application demande des données ... cela doit être effectué en un seul appel à la couche Requête. En retour, il obtiendra un seul DTO contenant toutes les données nécessaires. .. La raison en est que les données sont normalement interrogées bien plus que le comportement du domaine; ainsi, en les optimisant, vous améliorerez les performances perçues du système. "
Quentin-Starin
4
C'est pourquoi nous n'utiliserions pas de référentiel pour lire des données dans un système CQRS. Nous écririons une classe simple pour encapsuler une requête (en utilisant n'importe quelle technologie pratique ou nécessaire, souvent directement ADO.Net ou Linq2Sql ou SubSonic, ici), en retournant juste (toutes) les données nécessaires à la tâche à accomplir, pour éviter glisser les données à travers toutes les couches normales d'un référentiel DDD. Nous utiliserions un référentiel uniquement pour récupérer un agrégat si nous voulions envoyer une commande au domaine.
Quentin-Starin
9
"Je ne peux pas imaginer que quiconque préconise de renvoyer des agrégats complets lorsque vous n'en avez pas besoin." J'essaie de dire que vous avez parfaitement raison avec cette déclaration. Ne récupérez pas un agrégat complet d'informations lorsque vous n'en avez pas besoin. C'est le cœur même du CQRS appliqué à la DDD. Vous n'avez pas besoin d'un agrégat pour interroger. Obtenez les données via un mécanisme différent, puis procédez de manière cohérente.
Quentin-Starin
2
@qes En effet, la meilleure solution est de ne pas utiliser DDD pour les requêtes (read) :) Mais vous utilisez quand même DDD dans la partie Command, c'est-à-dire pour stocker ou mettre à jour des données. J'ai donc une question à vous poser: utilisez-vous toujours les référentiels avec des entités lorsque vous devez mettre à jour des données dans la base de données? Disons que vous ne devez modifier qu'une petite valeur dans la colonne (commutateur d'une certaine sorte), chargez-vous toujours l'entité entière dans le calque d'application, modifiez-vous une valeur (propriété), puis sauvegardez l'entité entière dans la base de données? Un peu d'overkill, aussi?
Andrew
8

Je me suis battu, et je suis toujours aux prises, pour savoir comment utiliser au mieux le modèle de référentiel dans une conception dirigée par un domaine. Après l'avoir utilisé maintenant pour la première fois, j'ai proposé les pratiques suivantes:

  1. Un référentiel devrait être simple; il est uniquement responsable du stockage des objets du domaine et de leur récupération. Toute autre logique doit figurer dans d'autres objets, tels que des usines et des services de domaine.

  2. Un référentiel se comporte comme une collection comme s'il s'agissait d'une collection en mémoire de racines agrégées.

  3. Un référentiel n'est pas un DAO générique, chaque référentiel a son interface unique et étroite. Un référentiel a souvent des méthodes de recherche spécifiques qui vous permettent de rechercher la collection en termes de domaine (par exemple: donnez-moi toutes les commandes ouvertes pour l'utilisateur X). Le référentiel lui-même peut être implémenté à l'aide d'un DAO générique.

  4. Idéalement, les méthodes de recherche ne renverront que des racines agrégées. Si cela est trop inefficace, il peut également renvoyer des objets de valeur en lecture seule qui ne contiennent exactement ce dont vous avez besoin (bien que ce soit un avantage si ces objets de valeur peuvent également être exprimés en termes de domaine). En dernier recours, le référentiel peut également être utilisé pour renvoyer des sous-ensembles ou des collections de sous-ensembles d'une racine agrégée.

  5. De tels choix dépendent des technologies utilisées, car vous devez trouver le moyen d'exprimer le plus efficacement possible votre modèle de domaine avec les technologies utilisées.

Kdeveloper
la source
C'est certainement un sujet complexe à coup sûr. Il est difficile de passer de la théorie à la pratique, en particulier lorsqu'elle combine deux théories distinctes et distinctes en une seule pratique.
Sebastian Patten
6

Je ne pense pas que votre méthode GetOrderHeaders vole le but du référentiel du tout.

DDD souhaite (entre autres choses) veiller à ce que vous obteniez ce dont vous avez besoin par le biais de la racine agrégée (vous n’auriez pas de OrderDetailsRepository, par exemple), mais cela ne vous limite pas à la façon dont vous le mentionnez.

Si OrderHeader est un concept de domaine, vous devez le définir en tant que tel et disposer des méthodes de référentiel appropriées pour les récupérer. Assurez-vous simplement que vous passez par la racine agrégée correcte quand vous le faites.

Eric King
la source
Peut-être que je confonds les concepts ici, mais ma compréhension du modèle de référentiel est de découpler la persistance du domaine, en utilisant une interface standard pour la persistance. Si vous devez ajouter des méthodes personnalisées pour une fonctionnalité spécifique, il semble que les choses soient couplées à nouveau.
Erik Funkenbusch le
1
Le mécanisme de persistance est découplé du domaine, mais pas ce qui est persistant. Si vous dites des mots tels que "nous devons répertorier les en-têtes de commande ici", vous devez modéliser OrderHeader dans votre domaine et fournir un moyen de les récupérer à partir de votre référentiel.
Eric King
Ne vous arrêtez pas non plus sur "l'interface standard de persistance". Un modèle de référentiel générique n’est pas suffisant pour toutes les applications possibles. Chaque application aura de nombreuses méthodes de référentiel au-delà des standards "GetById", "Enregistrer", etc. Ces méthodes sont le point de départ, pas le point final.
Eric King
4

Mon utilisation de DDD peut ne pas être considérée comme une "pure" DDD, mais j’ai adapté les stratégies du monde réel suivantes utilisant DDD par rapport à un magasin de données de base de données.

  • Une racine agrégée a un référentiel associé
  • Le référentiel associé est uniquement utilisé par cette racine agrégée (il n'est pas accessible au public)
  • Un référentiel peut contenir des appels de requête (par exemple, GetAllActiveOrders, GetOrderItemsForOrder).
  • Un service expose un sous-ensemble public du référentiel et d'autres opérations non crud (par exemple, transférer de l'argent d'un compte bancaire à un autre, LoadById, Rechercher / Chercher, CreateEntity, etc.).
  • J'utilise la racine -> Service -> Pile de référentiel. Un service DDD n’est censé être utilisé que pour tout ce qu’une entité ne peut pas répondre elle-même (par exemple, LoadById, TransferMoneyFromAccountToAccount), mais dans le monde réel, j’ai également tendance à utiliser d’autres services liés à CRUD (Save, Delete, Query) même root devrait être capable de "répondre / exécuter" eux-mêmes. Notez qu'il n'y a rien de mal à donner à une entité l'accès à un autre service racine global! Cependant, rappelez-vous que vous n'incluez pas dans un service (GetOrderItemsForOrder) mais l'incluez dans le référentiel afin que la racine d'agrégation puisse l'utiliser. Notez qu'un service ne doit exposer aucune requête ouverte comme le référentiel.
  • Je définis généralement un référentiel de manière abstraite dans le modèle de domaine (via une interface) et fournit une implémentation concrète séparée. Je définis complètement un service dans le modèle de domaine injectant dans un référentiel concret pour son utilisation.

** Vous n'êtes pas obligé de ramener un agrégat entier. Cependant, si vous voulez plus, vous devez demander à la racine, pas à un autre service ou référentiel. Ceci est un chargement paresseux et peut être effectué manuellement avec un chargement paresseux pauvre (en injectant le référentiel / service approprié dans la racine) ou en utilisant un ORM qui le supporte.

Dans votre exemple, je fournirais probablement un appel au référentiel contenant uniquement les en-têtes de commande si je voulais charger les détails lors d'un appel séparé. Notez qu'en disposant d'un "OrderHeader", nous introduisons en réalité un concept supplémentaire dans le domaine.

Mike Rowley
la source
3

Votre modèle de domaine contient votre logique métier dans sa forme la plus pure. Toutes les relations et opérations qui soutiennent les opérations commerciales. Ce qui vous manque dans votre carte conceptuelle, c'est l'idée de la couche de service d'application que la couche de service enveloppe autour du modèle de domaine et fournit une vue simplifiée du domaine métier (une projection si vous voulez) qui permet au modèle de domaine de changer en fonction des besoins. sans impact direct sur les applications utilisant la couche de service.

Aller plus loin. L'idée de l'agrégat est qu'il existe un seul objet, la racine de l'agrégat, chargé de maintenir la cohérence de l'agrégat. Dans votre exemple, la commande serait responsable de la manipulation de ses lignes de commande.

Pour votre exemple, la couche service exposerait une opération telle que GetOrdersForCustomer qui ne renverrait que ce qui est nécessaire pour afficher une liste récapitulative des commandes (lorsque vous les appelez OrderHeaders).

Enfin, le modèle de référentiel n'est pas simplement une collection, mais permet également des requêtes déclaratives. En C #, vous pouvez utiliser LINQ comme objet de requête ou la plupart des autres O / RM fournissent également une spécification d’objet de requête.

Un référentiel sert de médiateur entre les couches de mappage de domaine et de données, agissant comme une collection d'objets de domaine en mémoire. Les objets client construisent les spécifications de requête de manière déclarative et les soumettent au référentiel pour obtenir satisfaction. (à partir de la page du référentiel de Fowler )

Voyant que vous pouvez créer des requêtes sur le référentiel, il est également judicieux de fournir des méthodes pratiques qui gèrent les requêtes courantes. Par exemple, si vous souhaitez uniquement les en-têtes de votre commande, vous pouvez créer une requête qui renvoie uniquement l'en-tête et l'exposez à l'aide d'une méthode pratique dans vos référentiels.

J'espère que cela aide à clarifier les choses.

Michael Brown
la source
0

Je sais que c’est une vieille question, mais il semble que ma réponse soit différente.

Lorsque je crée un référentiel, il enveloppe généralement certaines requêtes mises en cache .

Fowler définit un référentiel comme un magasin de données qui utilise la sémantique de la collection et est généralement conservé en mémoire. Cela signifie créer un graphe d'objet entier.

Conservez ces référentiels dans la RAM de vos serveurs. Ils ne font pas que transmettre des objets à la base de données!

Si je suis dans une application Web avec une page répertoriant les commandes, sur laquelle vous pouvez cliquer pour afficher les détails, il est probable que je souhaite que ma page de liste des commandes contienne des détails sur les commandes (ID, nom, montant, date). pour aider un utilisateur à décider lequel il veut regarder.

À ce stade, vous avez deux options.

  1. Vous pouvez interroger la base de données et extraire exactement ce dont vous avez besoin pour créer la liste, puis interroger à nouveau pour extraire les détails individuels que vous souhaitez voir sur la page de détail.

  2. Vous pouvez faire une requête qui extrait toutes les informations et les met en cache. Sur la page suivante, demandez que vous lisiez à partir du serveur RAM au lieu de la base de données. Si l'utilisateur clique en arrière ou sélectionne la page suivante, vous ne faites toujours aucun déplacement vers la base de données.

En réalité, la manière dont vous la mettez en œuvre est juste cela, et les détails de la mise en œuvre. Si mon plus gros utilisateur a 10 commandes, je veux probablement utiliser l'option 2. Si je parle de 10 000 commandes, l'option 1 est nécessaire. Dans les deux cas ci-dessus et dans de nombreux autres cas, je souhaite que le référentiel masque ces détails d'implémentation.

A l'avenir, si je reçois un ticket pour indiquer à l'utilisateur combien il a dépensé en commandes ( données agrégées ) au cours du dernier mois sur la page de liste des commandes, est-ce que j'écrirais plutôt la logique pour calculer cela en SQL et faire un autre aller-retour pour la base de données ou préférez-vous la calculer à l'aide des données déjà présentes dans le serveur virtuel?

D'après mon expérience, les agrégats de domaine offrent d'énormes avantages.

  • Ils constituent une énorme réutilisation de code de partie qui fonctionne réellement.
  • Ils simplifient le code en conservant votre logique métier au cœur de la couche principale au lieu de devoir explorer une couche d'infrastructure pour que le serveur SQL le fasse.
  • Ils peuvent également accélérer considérablement vos temps de réponse en réduisant le nombre de requêtes à effectuer car vous pouvez facilement les mettre en cache.
  • Le SQL que j'écris est souvent beaucoup plus facile à gérer car je demande souvent tout et calcule le côté serveur.
WhiteleyJ
la source