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?
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.)
@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.
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:
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.
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 #.
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.)
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.
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 if
s 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.
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.
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.