DDD - la règle que les Entités ne peuvent pas accéder à des Référentiels directement
Dans le Domain Driven Design, il semble y avoir beaucoup de accord que les Entités ne doivent pas accéder aux Référentiels directement.
Cela venait Eric Evans Domain Driven Design livre, ou avait-il venir d'ailleurs?
Où il y aurait des bonnes explications pour le raisonnement derrière cela?
edit: Pour clarifier: je ne parle pas de la classique OO pratique de séparer les accès aux données dans une couche distincte de la logique métier - je parle de la disposition selon laquelle, DDD, les Entités ne sont pas censés parler de la couche d'accès aux données à tous (c'est à dire qu'ils ne sont pas censés contenir des références à des objets du Référentiel)
mise à jour: j'ai donné la prime à BacceSR parce que sa réponse semblait plus proche, mais je suis encore assez dans le noir à ce sujet. Si c'est un principe important, il devrait y avoir quelques bons articles à ce sujet en ligne quelque part, sûrement?
mise à jour: Mars 2013, les upvotes sur la question implique qu'il y a beaucoup d'intérêt dans le présent, et même si il y a eu beaucoup de réponses, je pense toujours qu'il n'y a plus de place pour les si des personnes ont des idées à ce sujet.
- Cette question n'a pas conduit à beaucoup de réponses jusqu'à présent, d'où le bounty : )
- 21 heures, à gauche sur la générosité et tout reste à jouer pour! Fondamentalement, la recherche d'une bonne explication (ou un lien vers une bonne explication) de cette "règle"
- Jetez un oeil à ma question stackoverflow.com/q/8269784/235715, c'est montrer une situation où il est difficile de capturer la logique, sans Entité ayant accès au dépôt. Mais je pense que les entités ne doivent pas avoir accès aux dépôts, et il y a une solution à ma situation lorsque le code peut être réécrite sans référentiel de référence, mais actuellement je ne peux pas penser à tout.
Vous devez vous connecter pour publier un commentaire.
Il y a un peu de confusion ici. Les référentiels de l'accès agrégats racines. Agréger les racines sont des entités. La raison pour cela est la séparation des préoccupations et de bonne superposition. Ce n'est pas logique sur de petits projets, mais si vous êtes sur une grande équipe que vous voulez dire, "Vous accédez à un produit par le biais du Référentiel de Produit. Le produit est un agrégat de la racine pour une collection d'entités, y compris les ProductCatalog objet. Si vous voulez mettre à jour le ProductCatalog vous devez passer par la ProductRepository."
De cette façon, vous avez très, très claire séparation de la logique métier et où les choses se faire jour. Vous n'avez pas un enfant qui n'est par lui-même et écrit l'ensemble de ce programme qui fait toutes ces choses compliquées pour le catalogue de produits et quand il s'agit de l'intégrer à l'amont du projet, vous êtes assis là à regarder et de réaliser tout doit être abandonné. Cela signifie également que lorsque les gens se joignent à l'équipe, d'ajouter de nouvelles fonctionnalités, ils savent où aller et comment structurer le programme.
Mais attendez! Référentiel se réfère également à la couche de persistance, comme dans le Modèle de Référentiel. Dans un monde meilleur, un Eric Evans Référentiel et le Modèle de Référentiel aurait séparé les noms, parce qu'ils ont tendance à se chevaucher un peu. Pour obtenir le modèle d'espace de stockage que vous avez contraste avec d'autres façons dans lequel les données sont accessibles, avec un service d'autobus ou d'un modèle d'événement système. Habituellement, lorsque vous arrivez à ce niveau, le Eric Evans Référentiel de définition va au bord du chemin et vous commencez à parler d'un contexte délimité. Chaque délimitée contexte est essentiellement sa propre application. Vous pourriez avoir un sophistiqué système d'autorisation pour l'obtention des choses dans le catalogue de produits. Dans la conception initiale du produit était le centre de la pièce, mais dans ce contexte délimité le catalogue de produits est. Vous pouvez encore accéder à des informations de produit et la mise à jour du produit par l'intermédiaire d'un bus de service, mais vous devez comprendre que d'un catalogue de produits à l'extérieur de la délimitée contexte peut signifier quelque chose de complètement différent.
Revenir à votre question initiale. Si vous avez accès à un référentiel à partir au sein d'une entité qu'il désigne l'entité qui n'est vraiment pas une entité commerciale, mais probablement quelque chose qui doit exister dans une couche de service. C'est parce que les entités sont des affaires de l'objet et doit se préoccuper de l'être comme beaucoup comme un DSL (domain specific language) que possible. Seulement de l'information d'affaires dans cette couche. Si vous êtes à la résolution des problèmes de performances, vous saurez à chercher ailleurs puisque seuls les renseignements de l'entreprise devrait être ici. Si tout à coup, vous avez des problèmes d'application ici, vous êtes ce qui rend très difficile pour étendre et maintenir une application, qui est vraiment le cœur de DDD: faire logiciels gérables.
Réponse au Commentaire 1: Droit, bonne question. Afin de ne pas tous la validation se produit dans le domaine de la couche. Sharp a un attribut "DomainSignature" qu'est-ce que vous voulez. Il est conscient de la persistance, mais étant un attribut maintient la couche domaine propre. Il s'assure que vous n'avez pas un double de l'entité, dans votre exemple, le même nom.
Mais parlons plus compliqué règles de validation. Disons que vous êtes Amazon.com. Avez-vous déjà commandé quelque chose d'expiration de carte de crédit? J'ai, où je n'ai pas mis à jour la carte et acheté quelque chose. Il accepte la commande et l'INTERFACE m'informe que tout est peachy. Environ 15 minutes plus tard, je vais vous recevrez un e-mail disant qu'il ya un problème avec ma commande, ma carte de crédit n'est pas valide. Ce qui se passe ici est que, dans l'idéal, il y a quelques regex de validation dans le domaine de la couche. Est-ce un bon numéro de carte de crédit? Si oui, persistent l'ordre. Cependant, il y a une validation supplémentaire à l'tâches de l'application de la couche, où un service externe est interrogé pour voir si le paiement peut être effectué sur la carte de crédit. Si non, n'est pas réellement expédier quoi que ce soit, de suspendre la commande et attendre le client. Tout ceci doit avoir lieu dans une couche de service.
N'ayez pas peur de créer des objets de validation à la couche de service que peut dépôts d'accès. Il suffit de le garder hors de la couche domaine.
Au premier abord, j'ai été de la persuasion pour permettre à certains de mes entités d'accès aux référentiels (ie. chargement différé sans un ORM). Plus tard, je suis venu à la conclusion que je ne devrais pas et que j'ai pu trouver d'autres moyens:
Vernon Vaughn dans le livre rouge de la mise en Œuvre de Domain-Driven Design fait référence à cette question dans deux endroits que je connais (note: ce livre est entièrement approuvé par Evans comme vous pouvez le lire dans l'avant-propos). Dans le Chapitre 7 sur les Services, il utilise un service de domaine et un cahier des charges pour les travaux autour de la nécessité d'un agrégat à l'utilisation d'un référentiel et d'un autre agrégat pour déterminer si un utilisateur est authentifié. Il est cité comme disant:
Vernon, Vaughn (2013-02-06). La Mise En Œuvre De Domain-Driven Design (Kindle Emplacement 6089). Pearson Education. L'Édition Kindle.
Et dans le Chapitre 10 sur les Agrégats, dans le l'article intitulé "Modèle de Navigation" il dit (juste après il recommande l'utilisation du mondial des Identifiants uniques pour référencer d'autres agrégats racines):
Il va sur de montrer un exemple de cela dans le code:
Il poursuit également mentionner encore une autre solution de la façon dont un service de domaine peut être utilisé dans un Agrégat méthode de commande avec double-dispatch. (Je ne peux pas le recommander assez combien il est bénéfique de lire son livre. Après vous avoir fatigué de fin lessly fouillant l'internet, de la fourchette au cours de la bien mérité de l'argent et de lire le livre.)
J'ai ensuite eu quelques discussion avec le toujours gracieuse Marco Pivetta @Ocramius qui m'a montré un peu de code sur tirant un cahier des charges à partir du domaine et de l'aide que:
1) Ce n'est pas recommandé:
2) Dans un domaine de service, c'est bon:
getFriends()
avant de faire quelque chose d'autre, il sera vide ou paresseux chargé. Si vide, alors cet objet allongé et dans un état non valide. Des idées sur ce point?C'est une très bonne question. Je vous attends à des discussions sur ce sujet. Mais je pense qu'il est mentionné dans plusieurs livres DDD et Jimmy nilssons et Eric Evans. Je suppose que c'est aussi visible à travers quelques exemples comment utiliser le dépôt de modèle.
MAIS permet de discuter. Je pense très valable pensée est pourquoi une entité savoir sur la façon de persister une autre entité? Important avec DDD est que chaque entité a la responsabilité de gérer sa propre connaissance "sphère" et ne sais rien à propos de la façon de lire ou d'écrire d'autres entités. Bien sûr, vous pouvez probablement juste ajouter un référentiel d'interface à Une Entité pour la lecture des Entités B. Mais le risque est que vous exposez connaissances pour comment persister B. est-ce qu'Une entité aussi faire de la validation sur B avant la persistance de B en db?
Comme vous pouvez le voir Une entité peut obtenir plus impliqué dans l'entité B du cycle de vie et qui peuvent ajouter de plus à la complexité du modèle.
Je suppose (sans exemple) que les tests unitaires seront plus complexes.
Mais je suis sûr qu'il y aura toujours des scénarios où vous êtes tentés d'utiliser les dépôts via des entités. Vous devez regarder chaque scénario pour faire un jugement valable. Des avantages et des Inconvénients. Mais le repository de l'entité solution, à mon avis commence avec beaucoup d'Inconvénients. Il faut être très spécial scénario avec des Pros qui équilibre les Cons....
Pourquoi séparer les données d'accès?
Du livre, je pense que les deux premières pages du chapitre Model Driven Design donne quelques raisons pour lesquelles vous souhaitez résumé technique détails de mise en œuvre de la mise en œuvre du modèle de domaine.
Ce qui semble être de tous dans le but d'éviter un "modèle d'analyse" qui dissocie la mise en œuvre effective du système.
De ce que je comprends du livre, il dit ceci "modèle d'analyse" peut être conçu sans tenir compte de mise en œuvre de logiciels. Une fois que les développeurs essaient de mettre en œuvre le modèle compris du côté des entreprises, ils forment leur propre abstractions en raison de la nécessité, provoquant un mur dans la communication et la compréhension.
Dans l'autre sens, les développeurs d'introduire trop de problèmes techniques dans le modèle de domaine peut provoquer cette fracture.
De sorte que vous pourriez considérer que la pratique de la séparation des préoccupations telles que la persévérance peut aider à protéger contre ces à la conception d'une analyse des modèles divergents. Si elle l'estime nécessaire d'introduire des choses comme la persistance dans le modèle, alors c'est un drapeau rouge. Peut-être que le modèle n'est pas pratique pour la mise en œuvre.
De citer:
"Le modèle unique réduit les chances d'erreur, parce que le dessin est une conséquence directe de l'soigneusement considéré comme modèle. La conception, et même le code lui-même, a la communicativeness d'un modèle."
La façon dont je suis interprète cela, si vous vous retrouviez avec plus de lignes de code en traitant avec des choses comme la base de données d'accès, vous perdez communicativeness.
Si le besoin d'avoir accès à une base de données est pour des choses comme la vérification de l'unicité, avoir un regard sur:
Udi Dahan: les fautes les plus grandes équipes lors de l'application de DDD
http://gojko.net/2010/06/11/udi-dahan-the-biggest-mistakes-teams-make-when-applying-ddd/
sous "Toutes les règles ne sont pas créés égaux"
et
Employant le Modèle du Domaine Modèle
http://msdn.microsoft.com/en-us/magazine/ee236415.aspx#id0400119
sous "Scénarios pour ne Pas Utiliser le Modèle de Domaine", qui aborde le même sujet.
Comment distinguer l'accès aux données
Le chargement de données à travers une interface
La "couche d'accès aux données" a été captée par une interface, qui vous vous appeler afin de récupérer des données requises:
Pros: L'interface sépare les "accès aux données" code de la plomberie, vous permettant de continuer à écrire des tests. L'accès aux données peuvent être traitées au cas par cas, permettant de meilleures performances que d'une stratégie générique.
Contre: Le code appelant doit assumer ce qui a été chargé et ce qui ne l'a pas.
Dire GetOrderLines retourne OrderLine objets avec une valeur null ProductInfo propriété pour des raisons de performances. Le développeur doit avoir une connaissance approfondie du code de l'interface.
J'ai essayé cette méthode sur des systèmes réels. Vous finissez par la modification de la portée de ce qui est chargé de tous les temps dans une tentative pour résoudre les problèmes de performance. Vous vous retrouvez jeter un oeil derrière l'interface de regarder le code d'accès aux données pour voir ce qui est et n'est pas chargé.
Maintenant, la séparation des préoccupations devraient permettre au développeur de se concentrer sur un aspect du code à la fois, autant que possible. L'interface technique élimine la FAÇON dont ces données sont chargées, mais pas de la QUANTITÉ de données est chargé, LORSQU'il est chargé, et OÙ il est chargé.
Conclusion: Assez faible séparation!
Chargement Différé
Les données sont chargées à la demande. Les appels de chargement de données est caché à l'intérieur de l'objet graphique lui-même, où l'accès à une propriété peut provoquer une requête sql à exécuter avant de retourner le résultat.
Pros: Le "QUAND, OÙ et COMMENT" de l'accès aux données est caché par le développeur en se concentrant sur le domaine de la logique. Il n'y a pas de code dans l'agrégat qui traite avec le chargement des données. La quantité de données chargées peuvent être la quantité exacte requise par le code.
Cons: Lorsque vous êtes touché par un problème de performance, il est difficile de fixer lorsque vous avez un générique de "one size fits all" solution. Le chargement paresseux peuvent causer une dégradation des performances dans l'ensemble, et la mise en œuvre de chargement différé peut être délicat.
Rôle D'Interface/Chargement Agressif
Chaque cas d'utilisation est rendue explicite par l'intermédiaire d'un Rôle D'Interface mis en œuvre par la classe d'agrégation, permettant le chargement de données des stratégies pour être manipulé par cas d'utilisation.
Stratégies de chargement peut ressembler à ceci:
Alors votre agrégat peut ressembler à:
La BillOrderFetchingStrategy est utiliser pour construire l'ensemble, et puis le total fait son travail.
Avantages: Permet le code personnalisé par cas d'utilisation, permettant une performance optimale. Est en ligne avec la L'Interface De La Ségrégation Principe. Pas de complexes et des exigences du code. Les agrégats de tests unitaires n'ont pas à imiter la stratégie de chargement. Générique de la stratégie de chargement peut être utilisé pour la majorité des cas (par exemple, une "charge de tous" de la stratégie) et de chargement spéciales stratégies peuvent être mises en œuvre si nécessaire.
Contre: Développeur a encore pour régler/examen des stratégies de chargement après le changement de domaine de code.
Avec la récupération de la stratégie de l'approche que vous pourriez encore trouver de changer la coutume de l'extraction de code pour un changement dans les règles d'affaires. Ce n'est pas une parfaite séparation des préoccupations, mais finira plus facile à gérer et c'est mieux que la première option. Les stratégies de chargement ne encapsuler le COMMENT, QUAND et OÙ les données sont chargées. Il a une meilleure séparation des préoccupations, sans perdre la flexibilité comme la taille unique convient à tous les paresseux de chargement approche.
J'ai trouvé ce blog pour avoir de très bons arguments contre l'encapsulation des Dépôts à l'intérieur des Entités:
http://thinkbeforecoding.com/post/2009/03/04/How-not-to-inject-services-in-entities
Ce qu'est une excellente question. Je suis sur le même chemin de la découverte, et la plupart des réponses à travers l'internet semblent apporter autant de problèmes car ils apportent des solutions.
Donc (au risque d'écrire quelque chose que je suis en désaccord avec un an à partir de maintenant) voici mes découvertes jusqu'à présent.
Tout d'abord, nous aimons les riche modèle de domaine, ce qui nous donne une haute l'identification de l' (de ce que l'on peut faire avec un agrégat) et lisibilité (expressif appels de méthode).
Nous voulons atteindre cet objectif sans injection de tous les services dans une entité du constructeur, parce que:
Alors, comment pouvons-nous faire cela? Ma conclusion est que méthode dépendances et double expédition fournir une bonne solution.
CreateCreditNote()
exige maintenant un service qui est responsable de la création des notes de crédit. Il utilise double expédition, entièrement déchargement le travail au responsable de service, tandis que maintien de l'identification de l' de laInvoice
entité.SetStatus()
a maintenant un simple dépendance sur un enregistreur, qui, évidemment, va effectuer partie du travail.Pour ce dernier, de rendre les choses plus facile sur le code du client, on pourrait à la place du journal par le biais d'un
IInvoiceService
. Après tout, la facture de journalisation semble assez intrinsèque à une facture. Une telleIInvoiceService
permet d'éviter la nécessité pour toutes sortes de mini-services pour les diverses opérations. L'inconvénient est qu'il devient obscure qu'est-ce exactement que le service sera ne. Il pourrait même commencer à look le double de l'expédition, tandis que la plupart du travail est vraiment encore fait dansSetStatus()
lui-même.On pourrait encore nommer le paramètre "logger", dans l'espoir de révéler à notre intention. Semble un peu faible, cependant.
À la place, j'opterais pour demander un
IInvoiceLogger
(comme nous le faisons déjà dans le code de l'échantillon) et ontIInvoiceService
à implémenter cette interface. Le code client peut tout simplement utiliser son uniqueIInvoiceService
pour tousInvoice
méthodes qui demandent une très particulier, de facture intrinsèque "mini-service", tandis que les signatures de méthode encore faire très clairement ce qu'ils demandent.Je remarque que je n'ai pas abordé dépôts exlicitly. Eh bien, l'enregistreur ou utilise un référentiel, mais permettez-moi aussi fournir un exemple explicite. Nous pouvons utiliser la même approche, si le dépôt n'est nécessaire qu'une méthode ou deux.
En fait, cela offre une alternative à la toujours gênant paresseux charges.
Mise à jour: j'ai laissé le texte ci-dessous à des fins historiques, mais je suggère de direction claire de paresseux charge à 100%.
Pour de vrai, fondée sur la propriété paresseux charges, je ne utilisent actuellement le constructeur de l'injection, mais en ignorant la persistance façon.
D'une part, un référentiel qui charge un
Invoice
à partir de la base de données peuvent avoir libre accès à une fonction qui va charger le correspondant des notes de crédit, et injecter cette fonction dans leInvoice
.D'autre part, le code qui crée une réelle nouveau
Invoice
simplement passer une fonction qui retourne une liste vide:(Une coutume
ILazy<out T>
pourrait nous débarrasser du vilain fonte àIEnumerable
, mais qui ne ferait que compliquer la discussion.)Je serais heureux d'entendre vos opinions, les préférences, et les améliorations!
Pour moi, cela semble être bien général des CRUES liées à la pratique plutôt que d'être spécifique à DDD.
Raisons que je peux penser sont:
J'ai appris le code de la programmation orientée objet, avant tout cela, séparée de la couche de buzz apparaissent, et mes premiers objets /classes carte directement à la base de données.
Finalement, j'ai ajouté une couche intermédiaire parce que j'ai eu à migrer vers un autre serveur de base de données. J'ai vu /entendu parler le même scénario plusieurs fois.
Je pense que séparer les données d'accès (un.k.un. "Référentiel") à partir d'une logique d'entreprise, est une de ces choses, qui ont été réinventée plusieurs fois, bien que le Domain Driven Design livre, faire beaucoup de "bruit".
J'utilise actuellement 3 couches (GUI, la Logique, l'Accès aux Données), comme beaucoup de développeurs, parce que c'est une bonne technique.
Séparer les données, dans un
Repository
couche (.k.un.Data Access
couche), peut être vu comme une technique de programmation, et pas seulement une règle à suivre.Comme de nombreuses méthodes, vous voudrez peut-être commencer par ne PAS mis en œuvre, et, finalement, mettre à jour votre programme, une fois que vous comprendre.
Citation:
L'Iliade n'était pas totalement inventé par Homère, Carmina Burana, n'était pas totalement inventé par Carl Orff, et dans les deux cas, la personne qui a mis les autres travailler, tous ensemble, a obtenu le crédit 😉
simplement Vernon Vaughn donne une solution:
C'est des vieux trucs. Le livre d'Eric vient de faire le buzz un peu plus.
Raison en est simple - esprit humain devient faible quand il fait face à vaguement liées à des contextes multiples. Ils mènent à l'ambiguïté (Amérique du Sud/Amérique du Nord moyen Sud/Amérique du Nord), ambiguïté conduit à la constante de cartographie de l'information à chaque fois l'esprit "touche" et qui résume aussi mauvais de la productivité et des erreurs.
Logique d'entreprise devrait être reflété aussi clairement que possible. Les clés étrangères, de la normalisation, le mapping objet-relationnel sont complètement différents de domaine - ces choses sont d'ordre technique, liés à l'informatique.
En analogie: si vous êtes d'apprendre à écrire à la main, vous ne devriez pas être accablés par comprendre d'où stylo a été fait, pourquoi l'encre tient sur le papier, le moment où le papier a été inventé et ce sont d'autres célèbres inventions Chinoises.
Raison est toujours la même je l'ai mentionné ci-dessus. Ici c'est juste un pas de plus. Pourquoi les entités devraient être en partie la persistance des ignorants, s'ils peuvent être (au moins presque) totalement? Moins d'un domaine sans rapport avec les préoccupations de notre modèle tient - plus d'espace pour respirer notre esprit reçoit quand il a ré-interpréter.
Citer, en Caroline du Lilientahl, "Modèles doivent éviter les cycles de" https://www.youtube.com/watch?v=eJjadzMRQAk, où elle se réfère au cyclique de dépendances entre les classes. En cas de dépôts à l'intérieur des agrégats, il y a une tentation de créer de dépendances cycliques de conveniance de la navigation d'objet que la seule raison. Le modèle mentionné ci-dessus par prograhammer, qui a été recommandé par Vernon Vaughn, où d'autres agrégats sont référencés par des id à la place des instances de la racine, (est-il un nom pour ce modèle?) suggère une alternative qui pourrait guider vers d'autres solutions.
Exemple de dépendance cyclique entre les classes (confession):
(Time0): Deux classes, l'Échantillon et le Bien, reportez-vous à l'autre (dépendance cyclique). Bien fait référence à l'Échantillon, et l'Échantillon renvoie à Bien, de commodité (parfois bouclage des échantillons, parfois en boucle tous les puits dans une plaque). Je ne pouvais pas imaginer des cas où l'Échantillon ne serait pas de référence de retour au Bien là où il est placé.
(1): Un an plus tard, de nombreux cas d'utilisation sont mises en œuvre .... et il y a maintenant le cas où l'Échantillon ne doit pas référence à l'eh Bien, il est placé. Il y a des plaques d'immatriculation temporaires à l'intérieur d'une étape de travail. Ici bien se réfère à un échantillon, qui à son tour se réfère à un puits sur une autre plaque. De ce fait, comportement bizarre se produit parfois lorsque quelqu'un essaie d'implémenter de nouvelles fonctionnalités. Prend le temps de pénétrer.
J'ai aussi été aidé par ce l'article mentionné ci-dessus sur les aspects négatifs de chargement paresseux.
Dans un monde idéal , DDD propose que les Entités ne devraient pas avoir de référence à des couches de données. mais nous ne vivons pas dans un monde idéal. Les domaines peuvent avoir besoin de se référer à d'autres objets du domaine de la logique d'entreprise avec qui ils pourraient ne pas avoir une dépendance. Il est logique que les entités se référer au dépôt de la couche de lire seul but, pour récupérer les valeurs.