web-dev-qa-db-fra.com

Modèles de domaine riches - comment, exactement, le comportement s'intègre-t-il?

Dans le débat sur les modèles de domaine riches contre anémiques, Internet regorge de conseils philosophiques mais ne contient que des exemples faisant autorité. L'objectif de cette question est de trouver des directives définitives et des exemples concrets de modèles de conception pilotés par domaine appropriés. (Idéalement en C #.)

Pour un exemple réel, cette implémentation de DDD semble erronée:

Les modèles de domaine WorkItem ci-dessous ne sont que des sacs de propriétés, utilisés par Entity Framework pour une base de données en premier code. Pour Fowler, c'est anémique .

La couche WorkItemService est apparemment une perception erronée courante des services de domaine; il contient toute la logique comportementale/métier du WorkItem. Selon Yemelyanov et d'autres, c'est procédural . (p. 6)

Donc, si ce qui suit est faux, comment puis-je le corriger?
Le comportement, c'est-à-dire AddStatusUpdate ou Checkout, devrait appartenir à la classe WorkItem correct?
Quelles dépendances le modèle WorkItem devrait-il avoir?

enter image description here

public class WorkItemService : IWorkItemService {
    private IUnitOfWorkFactory _unitOfWorkFactory;

    //using Unity for dependency injection
    public WorkItemService(IUnitOfWorkFactory unitOfWorkFactory) {
        _unitOfWorkFactory = unitOfWorkFactory;
    }

    public void AddStatusUpdate(int workItemId, int statusId) {

        using (var unitOfWork = _unitOfWorkFactory.GetUnitOfWork<IWorkItemUnitOfWork>()) {
            var workItemRepo = unitOfWork.WorkItemRepository;
            var workItemStatusRepo = unitOfWork.WorkItemStatusRepository;

            var workItem = workItemRepo.Read(wi => wi.Id == workItemId).FirstOrDefault();
            if (workItem == null)
                throw new ArgumentException(string.Format(@"The provided WorkItem Id '{0}' is not recognized", workItemId), "workItemId");

            var status = workItemStatusRepo.Read(s => s.Id == statusId).FirstOrDefault();
            if (status == null)
                throw new ArgumentException(string.Format(@"The provided Status Id '{0}' is not recognized", statusId), "statusId");

            workItem.StatusHistory.Add(status);

            workItemRepo.Update(workItem);
            unitOfWork.Save();
        }
    }
}

(Cet exemple a été simplifié pour être plus lisible. Le code est certainement encore maladroit, car c'est une tentative confuse, mais le comportement du domaine était: mettre à jour le statut en ajoutant le nouveau statut à l'historique des archives. En fin de compte, je suis d'accord avec les autres réponses, ce pourrait juste être géré par CRUD.)

Mise à jour

@AlexeyZimarev a donné la meilleure réponse, une vidéo parfaite sur le sujet en C # par Jimmy Bogard, mais elle a apparemment été déplacée dans un commentaire ci-dessous car elle n'a pas donné suffisamment d'informations au-delà du lien. J'ai un brouillon de mes notes résumant la vidéo dans ma réponse ci-dessous. N'hésitez pas à commenter la réponse avec des corrections. La vidéo dure une heure mais mérite d'être visionnée.

Mise à jour - 2 ans plus tard

Je pense que c'est un signe de la maturité naissante de DDD que même après l'avoir étudié pendant 2 ans, je ne peux toujours pas promettre que je connais la "bonne façon" de le faire. Le langage omniprésent, les racines agrégées et son approche de la conception axée sur le comportement sont les précieuses contributions de DDD à l'industrie. L'ignorance de la persistance et la recherche d'événements provoquent de la confusion, et je pense que la philosophie comme celle-ci l'empêche d'être adoptée plus largement. Mais si je devais refaire ce code, avec ce que j'ai appris, je pense qu'il ressemblerait à ceci:

enter image description here

J'accueille toujours avec plaisir toutes les réponses à ce message (très actif) qui fournissent tout code de bonnes pratiques pour un modèle de domaine valide.

89
RJB

La réponse la plus utile a été donnée par Alexey Zimarev et a obtenu au moins 7 votes positifs avant qu'un modérateur ne la place dans un commentaire sous ma question d'origine ....

Sa réponse:

Je vous recommande de regarder la session NDC 2012 de Jimmy Bogard "Crafting Wicked Domain Models" sur Vimeo. Il explique ce que devrait être un domaine riche et comment les implémenter dans la vie réelle en ayant un comportement dans vos entités. Les exemples sont très pratiques et tous en C #.

http://vimeo.com/4359819

J'ai pris quelques notes pour résumer la vidéo pour le bénéfice de mon équipe et pour fournir un peu plus de détails immédiats dans ce post. (La vidéo dure une heure, mais vaut vraiment chaque minute si vous avez le temps. Jimmy Bogard mérite beaucoup de crédit pour son explication.)

  • "Pour la plupart des applications ... nous ne savons pas qu'elles vont être complexes lorsque nous commencerons. Elles deviennent simplement ainsi."
    • La complexité augmente naturellement à mesure que du code et des exigences sont ajoutés. Les applications peuvent démarrer très simplement, comme CRUD, mais le comportement/les règles peuvent être intégrés.
    • "Ce qui est bien, c'est que nous n'avons pas besoin de commencer par un complexe. Nous pouvons commencer avec le modèle de domaine anémique, c'est juste des sacs de propriété, et avec seulement des techniques de refactorisation standard, nous pouvons évoluer vers un véritable modèle de domaine."
  • Modèles de domaine = objets métier. Comportement du domaine = règles métier.
  • Le comportement est souvent masqué dans une application - il peut être dans PageLoad, Button1_Click ou souvent dans des classes d'assistance comme 'FooManager' ou 'FooService'.
  • Les règles métier distinctes des objets de domaine "nous obligent à nous souvenir" de ces règles.
    • Dans mon exemple personnel ci-dessus, une règle métier est WorkItem.StatusHistory.Add (). Nous ne changeons pas seulement le statut, nous l'archivons pour l'audit.
  • Les comportements de domaine "éliminent les bogues dans une application beaucoup plus facilement que la simple écriture d'un tas de tests." Les tests nécessitent que vous sachiez écrire ces tests. Les comportements de domaine vous offrent les bons chemins à tester .
  • Les services de domaine sont des "classes d'assistance pour coordonner les activités entre différentes entités de modèle de domaine".
    • Services de domaine! = Comportement du domaine. Les entités ont un comportement, les services de domaine ne sont que des intermédiaires entre les entités.
  • Les objets de domaine ne doivent pas posséder l'infrastructure dont ils ont besoin (c'est-à-dire IOfferCalculatorService). Le service d'infrastructure doit être transmis au modèle de domaine qui l'utilise.
  • Les modèles de domaine devraient proposer de vous dire ce qu'ils peuvent faire, et ils ne devraient pouvoir faire que ces choses.
  • Les propriétés des modèles de domaine doivent être protégées par des setters privés, afin que seul le modèle puisse définir ses propres propriétés, à travers ses propres comportements . Sinon, c'est "promiscuité".
  • Les objets du modèle de domaine anémique, qui ne sont que des sacs de propriété pour un ORM, ne sont qu'un "placage fin - une version fortement typée de la base de données".
    • "Aussi simple soit-il d'obtenir une ligne de base de données dans un objet, c'est ce que nous avons."
    • "La plupart des modèles d'objets persistants ne sont que cela. Ce qui différencie un modèle de domaine anémique d'une application qui n'a pas vraiment de comportement, c'est si un objet a des règles métier, mais ces règles ne se trouvent pas dans un modèle de domaine. "
  • "Pour de nombreuses applications, il n’est pas vraiment nécessaire de créer une couche logique d’application réelle, c’est juste quelque chose qui peut parler à la base de données et peut-être un moyen simple de représenter les données qui y sont."
    • En d'autres termes, si vous ne faites que CRUD sans objets métier ou règles de comportement spéciaux, vous n'avez pas besoin de DDD.

N'hésitez pas à commenter tout autre point qui, selon vous, devrait être inclus ou si vous pensez que l'une de ces notes est hors de propos. J'ai essayé de citer directement ou de paraphraser autant que possible.

64
RJB

On ne peut pas répondre à votre question, car votre exemple est faux. Plus précisément, car il n'y a pas de comportement. Du moins pas dans le domaine de votre domaine. L'exemple de la méthode AddStatusUpdate n'est pas une logique de domaine, mais une logique qui utilise ce domaine. Ce type de logique est logique d'être à l'intérieur d'une sorte de service, qui gère les demandes extérieures.

Par exemple, s'il était nécessaire qu'un élément de travail spécifique ne puisse avoir que des statuts spécifiques, ou qu'il ne puisse avoir que N statuts, alors c'est la logique du domaine et devrait faire partie de WorkItem ou StatusHistory comme méthode.

La raison de votre confusion est que vous essayez d'appliquer une directive au code qui n'en a pas besoin. Les modèles de domaine ne sont pertinents que si vous avez beaucoup de logique de domaine complexe. Par exemple. logique qui fonctionne sur les entités elles-mêmes et découle des exigences. Si le code concerne la manipulation d'entités à partir de données externes, ce n'est pas, très probablement, une logique de domaine. Mais au moment où vous obtenez beaucoup de ifs en fonction des données et des entités avec lesquelles vous travaillez, c'est la logique du domaine.

L'un des problèmes de la véritable modélisation de domaine est qu'il s'agit de gérer des exigences complexes. Et en tant que tel, sa véritable puissance et ses avantages ne peuvent pas être présentés sur du code simple. Vous avez besoin de dizaines d'entités avec des tonnes d'exigences autour d'eux pour vraiment voir les avantages. Encore une fois, votre exemple est trop simple pour que le modèle de domaine brille vraiment.

Enfin, certains OT chose que je mentionnerais est qu'un véritable modèle de domaine avec une véritable conception OOP serait très difficile à maintenir en utilisant l'Entity Framework. Alors que les ORM étaient conçu avec le mappage de la vraie structure OOP aux structures relationnelles, il y a encore beaucoup de problèmes, et le modèle relationnel fuit souvent dans le modèle OOP. Même avec nHibernate, que je considère beaucoup plus puissant que EF, cela peut être un problème.

8
Euphoric

Votre hypothèse selon laquelle l'encapsulation de votre logique métier associée à WorkItem dans un "gros service" est un anti-modèle inhérent qui, selon moi, ne l'est pas nécessairement.

Quelles que soient vos réflexions sur le modèle de domaine anémique, les modèles et pratiques standard typiques d'une application Line of Business .NET encouragent une approche en couches transactionnelle composée de divers composants. Ils encouragent la séparation de la logique métier du modèle de domaine spécifiquement pour faciliter la communication d'un modèle de domaine commun à travers d'autres composants .NET ainsi que des composants sur différentes piles technologiques ou à travers des niveaux physiques.

Un exemple serait un service Web basé sur .NET SOAP qui communique avec une application cliente Silverlight qui se trouve avoir un DLL contenant des types de données simples. Cette Le projet d'entité de domaine peut être intégré dans un assemblage .NET ou un assemblage Silverlight, où les composants Silverlight intéressés qui ont ce DLL ne seront pas exposés à des comportements d'objet qui peuvent dépendre de composants uniquement disponibles pour le un service.

Quelle que soit votre position sur ce débat, il s'agit du modèle adopté et accepté par Microsoft et, à mon avis professionnel, ce n'est pas une mauvaise approche, mais un modèle d'objet qui définit son propre comportement n'est pas nécessairement non plus un anti-modèle. Si vous allez de l'avant avec cette conception, il est préférable de comprendre et de comprendre certaines des limites et des problèmes que vous pourriez rencontrer si vous devez vous intégrer à d'autres composants qui ont besoin de voir votre modèle de domaine. Dans ce cas particulier, vous souhaiterez peut-être qu'un traducteur convertisse votre modèle de domaine de style orienté objet en objets de données simples qui n'exposent pas certaines méthodes de comportement.

5
maple_shaft

Je me rends compte que cette question est assez ancienne, donc cette réponse est pour la postérité. Je veux répondre avec un exemple concret au lieu d'un basé sur la théorie.

Encapsulez le "changement d'état de l'élément de travail" sur la classe WorkItem comme ceci:

public SomeStatusUpdateType Status { get; private set; }

public void ChangeStatus(SomeStatusUpdateType status)
{
    // Maybe we designed this badly at first ;-)
    Status = status;       
}

Maintenant, votre classe WorkItem est responsable de se maintenir dans un état légal. L'implémentation est cependant assez faible. Le propriétaire du produit souhaite un historique de toutes les mises à jour de statut apportées au WorkItem.

Nous le changeons en quelque chose comme ceci:

private ICollection<SomeStatusUpdateType> StatusUpdates { get; private set; }
public SomeStatusUpdateType Status => StatusUpdates.OrderByDescending(s => s.CreatedOn).FirstOrDefault();

public void ChangeStatus(SomeStatusUpdateType status)
{
    // Better...
    StatusUpdates.Add(status);       
}

L'implémentation a radicalement changé, mais l'appelant de la méthode ChangeStatus ne connaît pas les détails d'implémentation sous-jacents et n'a aucune raison de se modifier.

Ceci est un exemple d'une entité de modèle de domaine riche, à mon humble avis.

5
Don