Où tracer la frontière entre délégation et encapsulation de la logique métier? Il me semble que plus nous déléguons, plus anémique nous devenons. Cependant, la délégation favorise également la réutilisation et le DRY principal. Alors, qu'est-ce qui est approprié pour déléguer et que devrait-il rester dans nos modèles de domaine?
Prenez les préoccupations suivantes comme exemples:
Autorisation . Si l'objet de domaine est responsable du maintien de ses règles de contrôle d'accès (comme une propriété CanEdit) ou doit-il être délégué à un autre composant/service seul responsable de la gestion de l'accès, par exemple IAuthorizationService.CanEdit (objet)? Ou devrait-il être une combinaison des deux? Peut-être que l'objet de domaine a une propriété CanEdit qui délègue à un IAuthorizationService interne pour effectuer le travail réel?
Validation . La même discussion que ci-dessus concerne la validation. Qui maintient les règles et qui est responsable de leur évaluation? D'une part, l'état de l'objet doit appartenir à cet objet et la validité est un état mais nous ne voulons pas réécrire le code utilisé pour évaluer les règles de chaque objet de domaine. Nous pourrions utiliser l'héritage dans ce cas ...
Création d'objets . Classe d'usine par rapport aux méthodes d'usine par rapport à la "nouvelle" d'une instance. Si nous utilisons une classe d'usine distincte, nous sommes en mesure d'isoler et d'encapsuler la logique de création, mais au détriment de l'ouverture de l'état de notre objet à l'usine. Cela peut être géré si notre couche de domaine se trouve dans un assembly séparé en exposant un constructeur interne utilisé par l'usine, mais cela devient un problème s'il existe plusieurs modèles de création. Et, si tout ce que fait l'usine, c'est d'appeler le bon constructeur, quel est l'intérêt d'avoir l'usine?
Les méthodes d'usine de la classe éliminent le problème d'ouverture de l'état interne de l'objet, mais comme elles sont statiques, nous ne pouvons pas briser les dépendances par l'injection d'une interface d'usine comme nous pouvons le faire avec une classe d'usine distincte.
Persistance . On pourrait faire valoir que si notre objet de domaine va exposer CanEdit tout en déléguant la responsabilité d'effectuer la vérification d'autorisation à une autre partie (IAuthorizationService), pourquoi ne pas avoir une méthode Save sur notre objet de domaine qui fait la même chose? Cela nous permettrait d'évaluer l'état interne de l'objet pour déterminer si l'opération peut être effectuée sans interrompre l'encapsulation. Bien sûr, cela nécessite que nous injections l'instance de référentiel dans notre objet de domaine, ce qui me sent un peu, alors déclenchons-nous un événement de domaine à la place et permettons-nous à un gestionnaire d'effectuer l'opération de persistance?
Tu vois où je vais avec ça?
Rockford Lhotka a une grande discussion sur les raisons pour lesquelles il a opté pour la voie Class-in-Charge pour son cadre CSLA et j'ai un peu d'histoire avec ce cadre et je peux voir son idée d'objets métier parallèles aux objets de domaine de nombreuses façons. Mais en essayant de devenir plus adhérent aux bons idéaux DDD, je me demande quand la collaboration devient trop.
Si je me retrouve avec un IAuthorizationService, IValidator, IFactory et IRepository pour ma racine agrégée, que reste-t-il? Le fait d'avoir une méthode de publication qui change l'état de l'objet de Brouillon à Publié est-il suffisant pour considérer la classe comme un objet de domaine non anémique?
Tes pensées?
La plupart de la confusion semble concerner des fonctionnalités qui ne devraient pas du tout exister dans le modèle de domaine:
Persistance ne devrait jamais être dans le modèle de domaine. Plus jamais. C'est la raison pour laquelle vous comptez sur des types abstraits tels que IRepository
si une partie du modèle a besoin de faire quelque chose comme récupérer une autre partie du modèle et utiliser l'injection de dépendance ou une technique similaire pour câbler l'implémentation. Alors rayez cela du dossier.
Autorisation ne fait pas généralement partie de votre modèle de domaine, sauf s'il fait réellement partie du domaine, par ex. si vous écrivez un logiciel de sécurité. La mécanique de qui est autorisé à effectuer quoi dans une application est normalement gérée au "bord" du niveau entreprise/domaine, les parties publiques avec lesquelles l'interface utilisateur et les éléments d'intégration sont réellement autorisés à parler - le contrôleur dans MVC, les services ou le système de messagerie lui-même dans un SOA ... vous obtenez l'image.
sines (et je suppose que vous voulez dire ici des usines abstraites) ne sont pas exactement mauvaises à avoir dans un modèle de domaine mais elles le sont presque toujours inutile. Normalement, vous n'avez une usine que lorsque la mécanique interne de la création d'objets peut changer. Mais vous n'avez qu'une seule implémentation du modèle de domaine, ce qui signifie qu'il n'y aura qu'un seul type de fabrique qui invoquera toujours les mêmes constructeurs et autres codes d'initialisation.
Vous pouvez avoir des usines "de commodité" si vous le souhaitez - des classes qui encapsulent des combinaisons communes de paramètres de constructeur, etc. - mais honnêtement, de manière générale, si vous avez beaucoup d'usines dans votre modèle de domaine, alors vous perdez des lignes de code.
Donc, une fois que vous avez fait tout cela, cela ne fait que valider. C'est le seul qui est un peu délicat.
Validation fait partie de votre modèle de domaine mais il fait également partie de tous les autres composants de l'application. Votre interface utilisateur et votre base de données auront leurs propres règles de validation, similaires mais différentes, basées sur un modèle conceptuel similaire mais différent. Il n'est pas vraiment spécifié si les objets doivent ou non avoir une méthode Validate
mais même s'ils le font, ils la délégueront généralement à une classe de validateur (pas d'interface - la validation est pas abstraite dans le modèle de domaine, il est fondamental).
Gardez à l'esprit que le validateur fait toujours techniquement partie du modèle; il n'a pas besoin d'être attaché à une racine agrégée car il ne contient aucune donnée ni aucun état. Les modèles de domaine sont des éléments conceptuels, qui se traduisent généralement physiquement par un assembly ou une collection d'assemblys. N'insistez pas sur le problème "anémique" si votre code de délégation réside à proximité immédiate du modèle objet; ça compte toujours.
Ce que tout cela se résume vraiment, c'est que si vous voulez faire DDD, vous devez comprendre quel est le domaine . Si vous parlez toujours de choses comme la persistance et l'autorisation, vous êtes sur la mauvaise voie. Le domaine représente l'état de fonctionnement d'un système - les objets et attributs physiques et conceptuels. Tout ce qui n'est pas directement pertinent pour les objets et les relations eux-mêmes n'appartient pas au modèle de domaine, point.
En règle générale, lorsque vous envisagez d'appartenir ou non à un modèle de domaine, posez-vous la question suivante:
"Cette fonctionnalité peut-elle changer pour des raisons purement techniques?" En d'autres termes, pas en raison d'un changement observable dans l'entreprise ou le domaine du monde réel?
Si la réponse est "oui", elle n'appartient pas au modèle de domaine. Cela ne fait pas partie du domaine.
Il y a de fortes chances que vous changiez un jour vos infrastructures de persistance et d'autorisation. Par conséquent, ils ne font pas partie du domaine, ils font partie de l'application. Cela s'applique également aux algorithmes, comme le tri et la recherche; vous ne devriez pas aller insérer une implémentation de code de recherche binaire dans votre modèle de domaine, car votre domaine ne concerne que le concept abstrait d'une recherche, pas son fonctionnement.
Si, après avoir supprimé tout ce qui n'a pas d'importance, vous trouvez que le modèle de domaine est vraiment anémique , alors cela devrait servir de une assez bonne indication que DDD est tout simplement le mauvais paradigme pour votre projet.
Certains domaines sont vraiment anémiques. Les applications de bookmarking social n'ont pas vraiment de "domaine" à proprement parler; tous vos objets sont essentiellement des données sans fonctionnalité. Un système de vente et CRM, en revanche, a un domaine assez lourd; lorsque vous chargez une entité Rate
, il y a une attente raisonnable que vous pouvez réellement faire des choses avec ce taux, comme l'appliquer à une quantité de commande et demandez-lui de comprendre les remises sur volume et les codes promotionnels et tout le reste.
Les objets de domaine qui contiennent uniquement des données signifient généralement que vous avez un modèle de domaine anémique, mais cela ne signifie pas nécessairement signifie que vous avez créé une mauvaise conception - cela pourrait simplement signifier que le domaine lui-même est anémique et que vous devriez utiliser une méthodologie différente.
Autorisation. Si l'objet de domaine est responsable du maintien de ses règles de contrôle d'accès
Non. L'autorisation est une préoccupation en soi. Les commandes qui ne seraient pas valides en raison d'un manque d'autorisations devraient être rejetées avant le domaine, le plus tôt possible - ce qui signifie souvent que nous voudrons même vérifier l'autorisation d'une commande potentielle afin de construire l'interface utilisateur (afin de ne même pas montrer à l'utilisateur la possibilité de modifier).
Le partage de stratégies d'autorisation entre plusieurs couches (dans l'interface utilisateur et plus haut dans un gestionnaire de services ou de commandes) est plus facile lorsque l'autorisation est segmentée séparément du modèle de domaine.
Une partie délicate qui peut être rencontrée est l'autorisation contextuelle, où une commande peut ou non être autorisée non seulement en fonction des rôles utilisateur mais également des données/règles métier.
Validation. La même discussion que ci-dessus concerne la validation.
Je dirais aussi non, pas dans le domaine (surtout). La validation se produit dans différents contextes et les règles de validation diffèrent souvent d'un contexte à l'autre. Il existe rarement un sens absolu simple de valide ou de non valide lorsque l'on considère les données encapsulées par un agrégat.
De même, comme l'autorisation, nous utilisons la logique de validation entre les couches - dans l'interface utilisateur, dans le gestionnaire de service ou de commande, etc. Encore une fois, il est plus facile d'utiliser DRY avec validation s'il s'agit d'un composant distinct . D'un point de vue pratique, la validation (en particulier lors de l'utilisation de frameworks) nécessite d'exposer des données qui autrement devraient être encapsulées et elle nécessite souvent d'attribuer des attributs personnalisés aux champs et aux propriétés. Je préfère de beaucoup qu'ils se trouvent sur d'autres classes que mes modèles de domaine.
Je préfère dupliquer certaines propriétés dans quelques classes similaires plutôt que d'essayer de forcer les exigences pour un cadre de validation dans mes entités. Cela finit inévitablement par gâcher les classes d'entités.
Création d'objets. Classe d'usine par rapport aux méthodes d'usine par rapport à la "nouvelle" d'une instance.
J'utilise une couche d'indirection. Dans mes projets les plus récents, il s'agit d'un gestionnaire de commandes + pour créer quelque chose, c'est-à-dire CreateNewAccountCommand
. Une alternative pourrait être de toujours utiliser une usine (bien que cela puisse être gênant si le reste d'une opération d'entités est exposé par une classe de service distincte de la classe d'usine).
En général, cependant, j'essaie d'être plus flexible avec les choix de conception pour la création d'objets. new
est facile et familier mais pas toujours suffisant. Je pense qu'il est important de faire preuve de jugement ici et de permettre à différentes parties d'un système d'utiliser différentes stratégies selon les besoins.
Persistance. ... pourquoi ne pas avoir une méthode Save sur notre objet domaine
C'est rarement une bonne idée; Je pense qu'il y a beaucoup d'expérience partagée pour soutenir cela.
Si je me retrouve avec un IAuthorizationService, IValidator, IFactory et IRepository pour ma racine agrégée, que reste-t-il? La méthode Publish modifie-t-elle l'état de l'objet de Draft à suffisamment publié pour considérer la classe comme un objet de domaine non anémique ???
Peut-être qu'un modèle de domaine n'est pas le bon choix pour cette partie de votre application.
OK, c'est parti pour moi. Je vais anticiper cela en disant que:
L'optimisation prématurée (et cela inclut la conception) peut souvent causer des problèmes.
IANMF (je ne suis pas Martin Fowler);)
Un sale petit secret est que sur les projets de petite taille (même de taille moyenne), c'est la cohérence de votre approche qui importera.
Autorisation
Pour moi, l'authentification et l'autorisation sont toujours une préoccupation transversale. Dans mon petit monde heureux Java world, qui est délégué à la sécurité Spring ou au framework Apache Shiro.
Validation Pour moi, la validation fait partie de l'objet, comme je le vois comme définissant ce qu'est l'objet.
par exemple. Un objet Car a 4 roues (OK il y a quelques exceptions étranges, mais permet d'ignorer la voiture étrange à 3 roues pour l'instant). Une voiture n'est tout simplement pas valide à moins qu'elle n'en ait 4 (dans mon monde), de sorte que la validation fait partie de la définition d'une voiture. Cela ne signifie pas que vous ne pouvez pas avoir de classes de validation d'assistance.
Dans mon monde heureux Java Java, j'utilise des frameworks de validation Bean et j'utilise des annotations simples sur la plupart de mes champs Bean. Il est alors facile de valider votre objet, quelle que soit la couche dans laquelle vous vous trouvez.
Création d'objet
Je regarde les cours d'usine avec prudence. Trop souvent, j'ai vu la classe xyxFactoryFactory
;)
J'ai tendance à simplement créer un objet new
selon les besoins, jusqu'à ce que je rencontre un cas où l'injection de dépendance est justifiée (et comme j'essaie de suivre une approche TDD, cela revient plus souvent qu'autrement).
Dans mon monde heureux Java qui est de plus en plus Guice, mais du printemps est toujours le roi ici.
Persistance
C'est donc un débat qui se déroule dans les cercles et les ronds-points et je suis toujours dans le même esprit.
Certains disent que si vous regardez l'objet d'une manière "pure", la persistance n'est pas une propriété fondamentale, c'est simplement une préoccupation extérieure.
D'autres estiment que vos objets de domaine implémentent implicitement une interface "persistante" (ouais je sais que je m'étire ici). Par conséquent, c'est bien d'avoir les diverses méthodes save
, delete
etc sur elles. Ceci est considéré comme une approche pragmatique et de nombreuses technologies ORM (JPA dans mon monde heureux Java monde) traitent les objets de cette manière.
Par souci de sécurité transversal, je m'assure que les autorisations de modification/suppression/ajout/quoi que ce soit sont correctement définies sur le service qui appelle la méthode save/update/delete sur l'objet. Si je suis vraiment paranoïaque, je pourrais même définir les autorisations sur l'objet de domaine lui-même.
HTH!
Jimmy Nilsson aborde ce sujet dans son livre sur DDD. Il a commencé avec un modèle anémique, est allé vers des modèles non anémiques sur un projet ultérieur et a finalement opté pour des modèles anémiques. Son raisonnement était que les modèles anémiques pouvaient être réutilisés dans plusieurs services avec des logiques commerciales différentes.
Le compromis est le manque de capacité de découverte. Les méthodes que vous pouvez utiliser pour opérer sur vos modèles anémiques sont réparties dans un ensemble de services situés ailleurs.
Cette question a été posée il y a longtemps, mais elle est balisée avec Domain Driven Design. Je pense que la question elle-même contient un malentendu fondamental de l'ensemble de la pratique et les réponses, y compris la réponse acceptée, perpétuent un malentendu fondamental.
Il n'y a pas de "modèle de domaine" dans une architecture DDD.
Prenons l'exemple de l'autorisation. Permettez-moi de vous demander de réfléchir à une question: imaginez que deux utilisateurs différents s'authentifient auprès de votre système. Un utilisateur est autorisé à modifier une certaine entité, mais pas l'autre. Pourquoi pas?
Je déteste les exemples simples et artificiels car ils confondent souvent plus qu'ils n'éclairent. Mais supposons que nous avons deux domaines différents. La première est une plate-forme CMS pour une agence de marketing. Cette agence a de nombreux clients qui ont tous du contenu en ligne qui doit être géré par des rédacteurs et des graphistes. Le contenu comprend des articles de blog ainsi que des pages de destination pour différents clients.
L'autre domaine est la gestion des stocks pour une entreprise de chaussures. Le système gère l'inventaire à partir de son arrivée du fabricant en France, vers les centres de distribution sur le continent américain, jusqu'aux magasins de détail sur les marchés locaux et enfin au client qui achète les chaussures au détail.
Si vous pensez que les règles d'autorisation sont les mêmes pour les deux sociétés, alors oui, ce serait un bon candidat pour un service en dehors du domaine. Mais je doute que les règles d'autorisation soient les mêmes. Même les concepts derrière les utilisateurs seraient différents. Certes, la langue serait différente. L'agence de marketing a probablement des rôles comme auteur de publication et propriétaire d'actifs, tandis que l'entreprise de chaussures a probablement des rôles de commis à l'expédition ou de responsable d'entrepôt ou de gérant de magasin.
Ces concepts sont probablement associés à toutes sortes de règles d'autorisation qui doivent être modélisées dans le domaine. Mais cela ne signifie pas qu'ils font tous partie du même modèle, même au sein de la même application. Parce que n'oubliez pas qu'il existe différents contextes délimités.
Ainsi, on pourrait peut-être considérer un modèle de domaine non anémique dans le contexte de l'autorisation comme différent du contexte d'acheminement des expéditions de chaussures vers les magasins à faible inventaire ou d'acheminement des visiteurs du site vers la page de destination appropriée en fonction de l'annonce sur laquelle ils ont cliqué.
Si vous vous retrouvez avec des modèles de domaine anémiques, vous devrez peut-être simplement consacrer plus de temps à la cartographie du contexte avant de commencer à écrire du code.