web-dev-qa-db-fra.com

Les agrégats DDD sont-ils vraiment une bonne idée dans une application Web?

Je plonge dans Domain Driven Design et certains des concepts que je rencontre ont beaucoup de sens en surface, mais quand j'y pense plus, je dois me demander 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 de ne pas avoir à gérer l'intégralité du modèle de domaine.

Cependant, quand j'y pense dans le contexte d'une application Web, nous frappons fréquemment la base de données pour retirer de petits sous-ensembles de données. Par exemple, une page ne peut lister que le nombre de commandes, avec des liens sur lesquels cliquer pour ouvrir la commande et voir ses identifiants de commande.

Si je comprends bien les agrégats, j'utilise généralement le modèle de référentiel pour renvoyer un ordre d'agrégat qui contiendrait les membres GetAll, GetByID, Delete et Save. Ok, ça sonne bien. Mais...

Si j'appelle GetAll pour répertorier toutes mes commandes, il me semble que ce modèle nécessiterait le renvoi de la liste complète des informations agrégées, des commandes complètes, des lignes de commande, etc ... Lorsque je n'ai besoin que d'un petit sous-ensemble de ces informations (juste des informations d'en-tête).

Suis-je en train de manquer quelque chose? Ou y a-t-il un certain niveau d'optimisation que vous utiliseriez ici? Je ne peux pas imaginer que quelqu'un préconiserait de renvoyer des agrégats entiers d'informations lorsque vous n'en avez pas besoin.

Certes, on pourrait créer des méthodes sur votre référentiel comme GetOrderHeaders, mais cela semble aller à l'encontre du but d'utiliser un modèle comme référentiel en premier lieu.

Quelqu'un peut-il clarifier cela pour moi?

MODIFIER:

Après beaucoup plus de recherches, je pense que la déconnexion ici est qu'un modèle de référentiel pur est différent de ce que la plupart des gens considèrent comme un référentiel.

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

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

La plupart des gens semblent considérer les référentiels comme des objets d'accès aux données glorifiés, où vous créez simplement des méthodes pour obtenir les données que vous souhaitez. Cela ne semble pas être l'intention 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 que c'est un concept beaucoup plus complexe que je ne le pensais au départ.

43
Erik Funkenbusch

N'utilisez pas votre modèle de domaine et vos agrégats pour les requêtes.

En fait, ce que vous posez est une question suffisamment courante pour qu'un ensemble de principes et de modèles soit établi pour éviter cela. Il s'appelle CQRS .

31
quentin-starin

J'ai eu du mal, et je continue de me battre, avec la meilleure façon d'utiliser le modèle de référentiel dans une conception pilotée par domaine. Après l'avoir utilisé pour la première fois, j'ai trouvé les pratiques suivantes:

  1. Un référentiel doit être simple; il est uniquement responsable du stockage des objets de domaine et de leur récupération. Toutes les autres logiques doivent se trouver dans d'autres objets, comme les usines et les 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 Finder 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 du Finder ne renverront que des racines agrégées. Si cela est inefficace, il peut également renvoyer des objets de valeur en lecture seule qui 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 un moyen d'exprimer le plus efficacement possible votre modèle de domaine avec les technologies utilisées.

8
Kdeveloper

Je ne pense pas que votre méthode GetOrderHeaders vienne du tout à l'encontre de l'objectif du référentiel.

DDD est soucieux (entre autres) de s'assurer que vous obtenez 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 dans la façon dont vous le mentionnez.

Si un OrderHeader est un concept de domaine, vous devez le définir comme 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 lorsque vous le faites.

6
Eric King

Mon utilisation de DDD peut ne pas être considérée comme DDD "pure" mais j'ai adapté les stratégies réelles suivantes en utilisant DDD par rapport à un magasin de données DB.

  • Une racine agrégée a un référentiel associé
  • Le référentiel associé n'est utilisé que 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, Search/Find, CreateEntity, etc.).
  • J'utilise la pile Root -> Service -> Repository. Un service DDD est supposé uniquement être utilisé pour tout ce à quoi une entité ne peut pas répondre elle-même (par exemple, LoadById, TransferMoneyFromAccountToAccount), mais dans le monde réel, j'ai tendance à coller également dans d'autres services liés à CRUD (Save, Delete, Query) même si le 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 agrégé! Cependant, n'oubliez pas que vous n'inclueriez pas dans un service (GetOrderItemsForOrder) mais l'incluriez dans le référentiel afin que la racine d'agrégat puisse l'utiliser. Notez qu'un service ne doit pas exposer de requêtes ouvertes comme le peut 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 l'interface) et je fournis une implémentation concrète distincte. Je définis complètement un service dans le modèle de domaine en injectant dans un référentiel concret pour son utilisation.

** Vous n'êtes pas obligé de rapporter un agrégat entier. Cependant, si vous en voulez plus, vous devez demander à la racine, pas à un autre service ou référentiel. Il s'agit d'un chargement paresseux et peut être effectué manuellement avec un chargement paresseux de mauvaise qualité (en injectant le référentiel/service approprié dans la racine) ou en utilisant et ORM qui prend en charge cela.

Dans votre exemple, je fournirais probablement un appel de référentiel qui n'a apporté que les en-têtes de commande si je voulais charger les détails sur un appel distinct. Notez qu'en ayant un "OrderHeader", nous introduisons en fait un concept supplémentaire dans le domaine.

4
Mike Rowley

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 Application Service Layer la couche de service entoure le modèle de domaine et fournit une vue simplifiée du domaine d'activité (une projection si vous voulez) qui permet le modèle de domaine à modifier au besoin 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 objet, la racine de l'agrégat, responsable du maintien de 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 de service exposerait une opération comme GetOrdersForCustomer qui ne retournerait que ce qui est nécessaire pour afficher une liste récapitulative des commandes (comme vous les appelez OrderHeaders).

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

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

Étant donné 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. C'est à dire. 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'exposer à partir d'une méthode pratique dans vos référentiels.

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

3
Michael Brown

Je sais que c'est une vieille question, mais il semble que j'aie trouvé une réponse différente.

Quand je crée un référentiel, il encapsule généralement quelques requêtes en cache.

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

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

Si je suis dans une application Web avec une page listant les commandes, sur laquelle vous pouvez cliquer pour voir les détails, il y a de fortes chances que je veuille que ma page de liste de commandes contienne des détails sur les commandes (ID, Nom, Montant, Date) pour aider un utilisateur à décider lequel il souhaite consulter.

À ce stade, vous avez deux options.

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

  2. Vous pouvez faire 1 requête qui récupère toutes les informations et les met en cache. Sur la page suivante, vous lisez à partir de la RAM des serveurs au lieu de la base de données. S'il l'utilisateur revient ou sélectionne la page suivante, vous ne faites toujours aucun voyage dans la base de données.

En réalité, la façon dont vous l'implémentez est juste cela, et les détails de l'implémentation. Si mon plus gros utilisateur a 10 commandes, je veux probablement aller avec 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.

À l'avenir, si je reçois un ticket pour dire à l'utilisateur combien il a dépensé pour les commandes ( données agrégées ) au cours du dernier mois sur la page de liste des commandes, est-ce que je préfère écrire la logique pour calculer cela en SQL et faire encore un aller-retour vers la base de données ou préférez-vous le calculer en utilisant les données qui sont déjà dans le serveur RAM?

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

  • Il s'agit d'une énorme réutilisation de code pièce qui fonctionne réellement.
  • Ils simplifient le code en gardant votre logique métier directement dans la couche principale au lieu d'avoir à parcourir 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 que vous devez effectuer, car vous pouvez facilement les mettre en cache.
  • Le SQL que j'écris est souvent beaucoup plus facile à entretenir car je demande souvent tout et calcule côté serveur.
0
WhiteleyJ