207 votes

DDD - la règle selon laquelle les entités ne peuvent pas accéder directement aux référentiels.

Dans la conception pilotée par le domaine, il semble y avoir lots de accord que les Entités ne doivent pas accéder directement aux Référentiels.

Est-ce que ça vient d'Eric Evans Conception pilotée par le domaine livre, ou bien cela vient-il d'ailleurs ?

Où peut-on trouver de bonnes explications sur le raisonnement qui le sous-tend ?

edit : Pour clarifier : je ne parle pas de la pratique OO classique qui consiste à séparer l'accès aux données dans une couche distincte de la logique métier - je parle de l'arrangement spécifique selon lequel, dans DDD, les Entités ne sont pas du tout censées parler à la couche d'accès aux données (c'est-à-dire qu'elles ne sont pas censées contenir des références aux objets du Référentiel).

mise à jour : J'ai donné la prime à BacceSR parce que sa réponse semblait la plus proche, mais je suis toujours dans l'ignorance à ce sujet. Si c'est un principe si important, il devrait y avoir de bons articles à ce sujet quelque part en ligne, non ?

mise à jour : mars 2013, les upvotes sur la question impliquent qu'il y a beaucoup d'intérêt pour cela, et même s'il y a eu beaucoup de réponses, je pense qu'il y a encore de la place pour plus si les gens ont des idées à ce sujet.

0 votes

Jetez un coup d'œil à ma question stackoverflow.com/q/8269784/235715 Il s'agit d'une situation où il est difficile de capturer la logique, sans que l'entité n'ait accès au référentiel. Bien que je pense que les entités ne devraient pas avoir accès aux référentiels, et qu'il y a une solution à ma situation lorsque le code peut être réécrit sans référence au référentiel, mais pour le moment je n'en vois pas.

1 votes

Je ne sais pas d'où il vient. Ce que j'en pense : Je pense que ce malentendu vient de personnes qui ne comprennent pas ce qu'est le DDD. Cette approche ne sert pas à mettre en œuvre des logiciels, mais à les concevoir (domaine conception). À l'époque, nous avions des architectes et des implémenteurs, mais aujourd'hui, il n'y a plus que des développeurs de logiciels. DDD est destiné aux architectes. Et lorsqu'un architecte conçoit un logiciel, il a besoin d'un outil ou d'un modèle pour représenter une mémoire ou une base de données pour les développeurs qui mettront en œuvre la conception préparée. Mais la conception elle-même (d'un point de vue commercial) n'a pas besoin d'un référentiel.

53voto

kertosis Points 305

Il y a un peu de confusion ici. Les référentiels accèdent aux racines agrégées. Les racines agrégées sont des entités. La raison en est la séparation des préoccupations et une bonne stratification. Cela n'a pas de sens pour les petits projets, mais si vous êtes dans une grande équipe, vous voulez dire : "Vous accédez à un produit par le biais du référentiel de produits. Product est un Root agrégé pour une collection d'entités, y compris l'objet ProductCatalog. Si vous voulez mettre à jour le ProductCatalog, vous devez passer par le ProductRepository".

De cette façon, vous avez une séparation très, très claire sur la logique d'entreprise et où les choses sont mises à jour. Il n'y a pas un gamin qui écrit tout seul ce programme qui fait toutes ces choses compliquées sur le catalogue de produits et quand il faut l'intégrer au projet en amont, vous êtes assis là à le regarder et vous réalisez que tout doit être abandonné. Cela signifie également que lorsque des personnes rejoignent l'équipe et ajoutent de nouvelles fonctionnalités, elles savent où aller et comment structurer le programme.

Mais attendez ! Repository fait également référence à la couche de persistance, comme dans le Repository Pattern. Dans un monde meilleur, le référentiel d'Eric Evans et le modèle de référentiel auraient des noms séparés, parce qu'ils ont tendance à se chevaucher un peu. Pour obtenir le modèle de référentiel, vous devez contraster avec d'autres façons d'accéder aux données, avec un bus de service ou un système de modèle d'événement. Habituellement, lorsque vous arrivez à ce niveau, la définition du référentiel d'Eric Evans est mise de côté et vous commencez à parler d'un contexte délimité. Chaque contexte délimité est essentiellement sa propre application. Vous pouvez avoir un système d'approbation sophistiqué pour placer des produits dans le catalogue de produits. Dans votre conception initiale, le produit était la pièce centrale, mais dans ce contexte délimité, c'est le catalogue de produits qui l'est. Vous pouvez toujours accéder aux informations sur les produits et les mettre à jour via un bus de services, mais vous devez comprendre qu'un catalogue de produits hors du contexte délimité peut avoir une signification complètement différente.

Revenons à votre question initiale. Si vous accédez à un référentiel à partir d'une entité, cela signifie que l'entité n'est pas vraiment une entité métier, mais probablement quelque chose qui devrait exister dans une couche de service. C'est parce que les entités sont des objets d'entreprise et devraient se rapprocher le plus possible d'un DSL (domain specific language). Cette couche ne contient que des informations commerciales. Si vous êtes en train de résoudre un problème de performance, vous saurez qu'il faut chercher ailleurs puisque seules les informations commerciales doivent se trouver ici. Si soudainement, vous avez des problèmes d'application ici, vous rendez très difficile l'extension et la maintenance d'une application, ce qui est vraiment le cœur de DDD : faire des logiciels maintenables.

Réponse au commentaire 1 : Oui, bonne question. Donc pas tous la validation se produit dans la couche de domaine. Sharp possède un attribut "DomainSignature" qui fait ce que vous voulez. Il est conscient de la persistance, mais le fait qu'il s'agisse d'un attribut permet de garder la couche de domaine propre. Il permet de s'assurer qu'il n'y a pas de doublon d'entité avec, dans votre exemple, le même nom.

Mais parlons de règles de validation plus compliquées. Disons que vous êtes Amazon.com. Avez-vous déjà commandé quelque chose avec une carte de crédit expirée ? Moi, je l'ai fait, alors que je n'avais pas mis à jour la carte et que j'avais acheté quelque chose. La commande est acceptée et l'interface utilisateur m'informe que tout est parfait. Environ 15 minutes plus tard, je reçois un e-mail m'indiquant qu'il y a un problème avec ma commande, ma carte de crédit n'est pas valide. Ce qui se passe ici est que, idéalement, il y a une validation regex dans la couche de domaine. S'agit-il d'un numéro de carte de crédit correct ? Si oui, la commande est maintenue. Cependant, il y a une validation supplémentaire au niveau de la couche des tâches applicatives, où un service externe est interrogé pour voir si le paiement peut être effectué avec la carte de crédit. Si ce n'est pas le cas, n'expédiez rien, suspendez la commande et attendez le client. Tout cela devrait avoir lieu dans une couche de service.

N'ayez pas peur de créer des objets de validation au niveau de la couche service qui peut les dépôts d'accès. Il suffit de le garder hors de la couche de domaine.

20 votes

Merci. Mais je devrais m'efforcer d'intégrer autant de logique métier que possible dans les entités (et leurs usines, spécifications, etc.), n'est-ce pas ? Mais si aucune d'entre elles n'est autorisée à récupérer des données via les référentiels, comment suis-je censé écrire une logique métier (raisonnablement compliquée) ? Par exemple : Un utilisateur de Chatroom n'est pas autorisé à changer son nom pour un nom qui a déjà été utilisé par quelqu'un d'autre. J'aimerais que cette règle soit intégrée dans l'entité ChatUser, mais ce n'est pas très facile à faire si on ne peut pas accéder au référentiel à partir de là. Alors que dois-je faire ?

1 votes

Ma réponse était plus longue que la boîte de commentaires ne le permettait, voir l'édition.

9 votes

L'entité doit savoir comment se protéger. Elle doit notamment s'assurer qu'elle ne peut pas se retrouver dans un état invalide. Ce que vous décrivez avec l'utilisateur de la salle de discussion est une logique métier qui s'ajoute à la logique dont dispose l'entité pour se maintenir valide. Une logique métier telle que celle que vous souhaitez est vraiment à sa place dans un service de salon de discussion, et non dans l'entité ChatUser.

28voto

Magnus Backeus Points 1149

C'est une très bonne question. J'attends avec impatience une discussion à ce sujet. Mais je pense que c'est mentionné dans plusieurs livres de DDD et Jimmy Nilssons et Eric Evans. Je suppose que l'on peut aussi voir à travers des exemples comment utiliser le modèle reposistory.

MAIS discutons-en. Je pense qu'une réflexion très valable est de savoir pourquoi une entité devrait savoir comment faire persister une autre entité ? Ce qui est important avec DDD, c'est que chaque entité a la responsabilité de gérer sa propre "sphère de connaissances" et ne devrait rien savoir sur la façon de lire ou d'écrire d'autres entités. Bien sûr, vous pouvez probablement ajouter une interface de référentiel à l'entité A pour lire les entités B. Mais le risque est que vous exposez la connaissance sur la façon de persister B. L'entité A fera-t-elle aussi une validation sur B avant de persister B dans la base de données ?

Comme vous pouvez le constater, l'entité A peut s'impliquer davantage dans le cycle de vie de l'entité B, ce qui peut rendre le modèle plus complexe.

Je suppose (sans aucun exemple) que les tests unitaires seront plus complexes.

Mais je suis sûr qu'il y aura toujours des scénarios où vous serez tenté d'utiliser des référentiels via des entités. Vous devez examiner chaque scénario pour porter un jugement valable. Le pour et le contre. Mais à mon avis, la solution référentiel-entités commence avec beaucoup de contre. Il doit s'agir d'un scénario très spécial avec des avantages qui compensent les inconvénients.....

2 votes

C'est un bon point. Dans le modèle de domaine de la vieille école, l'entité B serait probablement responsable de sa propre validation avant de se laisser persister.

0 votes

W d .

0 votes

L'exemple de SmartCA semble intéressant, dans quel chapitre se trouve-t-il ? (les téléchargements de code sont classés par chapitre)

14voto

tomsyd Points 111

Pourquoi séparer l'accès aux données ?

Dans le livre, je pense que les deux premières pages du chapitre Model Driven Design justifient pourquoi vous voulez abstraire les détails d'implémentation technique de l'implémentation du modèle de domaine.

  • Vous souhaitez maintenir une connexion étroite entre le modèle de domaine et le code.
  • La séparation des préoccupations techniques permet de prouver que le modèle est pratique à mettre en œuvre.
  • Vous voulez que le langage omniprésent s'infiltre dans la conception du système.

Tout cela semble avoir pour but d'éviter un "modèle d'analyse" distinct qui se détache de la mise en œuvre réelle du système.

D'après ce que j'ai compris du livre, il est dit que ce "modèle d'analyse" peut finir par être conçu sans tenir compte de la mise en œuvre du logiciel. Une fois que les développeurs essaient d'implémenter le modèle compris par l'entreprise, ils forment leurs propres abstractions par nécessité, ce qui crée un mur dans la communication et la compréhension.

Dans l'autre sens, les développeurs qui introduisent trop de préoccupations techniques dans le modèle de domaine peuvent également provoquer cette fracture.

On peut donc considérer que la pratique de la séparation des préoccupations, comme la persistance, peut aider à se prémunir contre la divergence de ces modèles de conception et d'analyse. S'il est nécessaire d'introduire des éléments comme la persistance dans le modèle, c'est un signal d'alarme. Le modèle n'est peut-être pas pratique à mettre en œuvre.

Je cite :

"Le modèle unique réduit les risques d'erreur, car la conception est désormais une excroissance directe du modèle soigneusement étudié. La conception, et même le code lui-même, ont la communicabilité d'un modèle."

De la façon dont j'interprète cela, si vous vous retrouvez avec plus de lignes de code traitant de choses comme l'accès aux bases de données, vous perdez cette communicativité.

Si l'accès à une base de données est nécessaire pour des choses comme la vérification de l'unicité, jetez un coup d'œil à :

Udi Dahan : les plus grandes erreurs des équipes lors de l'application de DDD

http://gojko.net/2010/06/11/udi-dahan-the-biggest-mistakes-teams-make-when-applying-ddd/

sous la rubrique "Toutes les règles ne sont pas égales".

et

Utilisation du modèle de domaine

http://msdn.microsoft.com/en-us/magazine/ee236415.aspx#id0400119

sous la rubrique "Scénarios pour ne pas utiliser le modèle de domaine", qui aborde le même sujet.

Comment séparer l'accès aux données

Chargement des données via une interface

La "couche d'accès aux données" a été abstraite grâce à une interface, que vous appelez pour récupérer les données requises :

var orderLines = OrderRepository.GetOrderLines(orderId);

foreach (var line in orderLines)
{
     total += line.Price;
}

Avantages : L'interface sépare le code de plomberie de l'accès aux données, ce qui vous permet de continuer à écrire des tests. L'accès aux données peut être géré au cas par cas, ce qui permet d'obtenir de meilleures performances qu'avec une stratégie générique.

Contre : Le code d'appel doit supposer ce qui a été chargé et ce qui ne l'a pas été.

Pour des raisons de performance, GetOrderLines renvoie des objets OrderLine avec une propriété ProductInfo nulle. Le développeur doit avoir une connaissance approfondie du code derrière l'interface.

J'ai essayé cette méthode sur des systèmes réels. Vous finissez par changer la portée de ce qui est chargé tout le temps dans une tentative de résoudre les problèmes de performance. Vous finissez par jeter un coup d'œil derrière l'interface pour regarder le code d'accès aux données afin de voir ce qui est chargé et ce qui ne l'est pas.

Or, la séparation des préoccupations devrait permettre au développeur de se concentrer sur un seul aspect du code à la fois, dans la mesure du possible. La technique de l'interface supprime la question de savoir COMMENT ces données sont chargées, mais pas celle de savoir QUELLE QUANTITÉ de données est chargée, QUAND elle est chargée et OÙ elle est chargée.

Conclusion : Une séparation assez faible !

Chargement paresseux

Les données sont chargées à la demande. Les appels pour charger les données sont cachés dans le graphe d'objets lui-même, où l'accès à une propriété peut provoquer l'exécution d'une requête sql avant de renvoyer le résultat.

foreach (var line in order.OrderLines)
{
    total += line.Price;
}

Pour : Le "QUAND, OÙ et COMMENT" de l'accès aux données est caché au développeur qui se concentre sur la logique du domaine. Il n'y a pas de code dans l'agrégat qui traite du chargement des données. La quantité de données chargée peut être la quantité exacte requise par le code.

Contre : Lorsque vous êtes confronté à un problème de performance, il est difficile de le résoudre lorsque vous avez une solution générique "taille unique". Le chargement paresseux peut entraîner une baisse générale des performances, et sa mise en œuvre peut être délicate.

Interface avec le rôle/la recherche

Chaque cas d'utilisation est rendu explicite par un Interface des rôles mis en œuvre par la classe d'agrégat, ce qui permet de gérer les stratégies de chargement des données en fonction des cas d'utilisation.

La stratégie de récupération peut ressembler à ceci :

public class BillOrderFetchingStrategy : ILoadDataFor<IBillOrder, Order>
{
    Order Load(string aggregateId)
    {
        var order = new Order();

        order.Data = GetOrderLinesWithPrice(aggregateId);

        return order;
    }

}

Alors votre agrégat peut ressembler à :

public class Order : IBillOrder
{
    void BillOrder(BillOrderCommand command)
    {
        foreach (var line in this.Data.OrderLines)
        {
            total += line.Price;
        }

        etc...
    }
}

La stratégie BillOrderFetchingStrategy est utilisée pour construire l'agrégat, puis l'agrégat fait son travail.

Pour : Permet un code personnalisé par cas d'utilisation, ce qui permet des performances optimales. Est en ligne avec le Principe de ségrégation des interfaces . Aucune exigence de code complexe. Les tests unitaires des agrégats n'ont pas à imiter la stratégie de chargement. Une stratégie de chargement générique peut être utilisée pour la majorité des cas (par exemple, une stratégie "load all") et des stratégies de chargement spéciales peuvent être mises en œuvre si nécessaire.

Inconvénient : le développeur doit toujours ajuster/réviser la stratégie de récupération après avoir modifié le code du domaine.

Avec l'approche de la stratégie d'extraction, vous pouvez toujours vous retrouver à modifier le code d'extraction personnalisé en cas de changement des règles de gestion. Il ne s'agit pas d'une séparation parfaite des préoccupations, mais elle sera plus facile à maintenir et est meilleure que la première option. La stratégie d'extraction encapsule le COMMENT, le QUAND et le OÙ des données sont chargées. Elle assure une meilleure séparation des préoccupations, sans perdre la flexibilité de l'approche de chargement paresseux à taille unique.

1 votes

Merci, je vais vérifier les liens. Mais dans votre réponse, vous confondez "séparation des préoccupations" et "pas d'accès du tout" ? La plupart des gens seraient certainement d'accord pour dire que la couche de persistance devrait être séparée de la couche dans laquelle se trouvent les entités. Mais ce n'est pas la même chose que de dire "les entités ne devraient même pas être en mesure de voir la couche de persistance, même par le biais d'une interface agnostique de mise en œuvre très générale".

0 votes

Que vous chargiez des données par l'intermédiaire d'une interface ou non, vous êtes toujours concerné par le chargement de données tout en mettant en œuvre des règles de gestion. Je suis d'accord avec le fait que beaucoup de gens appellent encore cela la séparation des préoccupations, mais peut-être que le principe de la responsabilité unique aurait été un meilleur terme à utiliser.

1 votes

Je ne sais pas trop comment interpréter votre dernier commentaire, mais je pense que vous suggérez que les données ne devraient pas être chargées pendant le traitement des règles de gestion ? Je vois que cela rendrait les règles plus "pures". Mais de nombreux types de règles de gestion vont devoir faire référence à d'autres données - suggérez-vous qu'elles soient chargées à l'avance par un objet séparé ?

12voto

ahaaman Points 176

J'ai trouvé dans ce blog d'assez bons arguments contre l'encapsulation des référentiels dans les entités :

http://thinkbeforecoding.com/post/2009/03/04/How-not-to-inject-services-in-entities

1voto

umlcat Points 2025

J'ai appris à coder la programmation orientée objet avant l'apparition de toutes ces couches séparées, et mes premiers objets/classes DID sont directement liés à la base de données.

Finalement, j'ai ajouté une couche intermédiaire parce que je devais migrer vers un autre serveur de base de données. J'ai vu / entendu parler du même scénario à plusieurs reprises.

Je pense que la séparation de l'accès aux données et de la logique de l'entreprise est l'une de ces choses qui ont été réinventées plusieurs fois, mais le livre Domain Driven Design fait beaucoup de "bruit".

J'utilise actuellement 3 couches (GUI, Logique, Accès aux données), comme beaucoup de développeurs le font, car c'est une bonne technique.

Citation : L'Iliade n'a pas été totalement inventée par Homère, Carmina Burana n'a pas été totalement inventée par Carl Orff, et dans les deux cas, la personne qui a fait travailler les autres, tous ensemble, a obtenu le crédit ;-)

2 votes

Merci, mais ma question ne porte pas sur la séparation de l'accès aux données et de la logique commerciale - c'est une chose très claire qui fait l'objet d'un large consensus. Je me demande pourquoi, dans les architectures DDD telles que S#arp, les entités ne sont pas autorisées à "parler" à la couche d'accès aux données. Il s'agit d'une disposition intéressante sur laquelle je n'ai pas pu trouver beaucoup de discussions.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X