В дебатах моделей предметной области Rich против Anemic Интернет полон философских советов, но не хватает авторитетных примеров. Цель этого вопроса - найти четкие рекомендации и конкретные примеры правильных моделей проектирования на основе доменов. (В идеале в C #.)
Для реального примера эта реализация DDD кажется неправильной:
Приведенные ниже модели доменов WorkItem представляют собой не что иное, как пакеты свойств, используемые Entity Framework для базы данных с первым кодом. По Фаулеру, это анемично .
Уровень WorkItemService, по-видимому, является распространенным заблуждением доменных служб; он содержит всю поведенческую / бизнес-логику для WorkItem. По Емельянову и другим, это процедурно . (стр. 6)
Так что, если ниже не так, как я могу сделать это правильно?
Поведение, т.е. AddStatusUpdate или Checkout , должно принадлежать в классе WorkItem правильно?
Какие зависимости должна иметь модель WorkItem?
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();
}
}
}
(Этот пример был упрощен, чтобы быть более читабельным. Код определенно все еще неуклюжий, потому что это ошибочная попытка, но поведение домена было следующим: обновить статус, добавив новый статус в историю архива. В конечном счете, я согласен с другими ответами, это может быть обработан CRUD.)
Обновить
@AlexeyZimarev дал лучший ответ - идеальное видео на эту тему в C # от Джимми Богарда, но, очевидно, оно было перемещено в комментарий ниже, потому что оно не давало достаточно информации, кроме ссылки. У меня есть черновик моих заметок с кратким изложением видео в моем ответе ниже. Пожалуйста, не стесняйтесь комментировать ответ с любыми исправлениями. Видео длится час, но его стоит посмотреть.
Обновление - 2 года спустя
Я думаю, что это признак зарождающейся зрелости DDD, что даже после изучения его в течение 2 лет, я все еще не могу обещать, что знаю «правильный способ» сделать это. Вездесущий язык, совокупные корни и его подход к поведенческому дизайну являются ценным вкладом DDD в отрасль. Постоянное невежество и источники событий приводят к путанице, и я думаю, что такая философия удерживает ее от более широкого принятия. Но если бы мне пришлось делать этот код снова, с тем, что я узнал, я думаю, это выглядело бы примерно так:
Я по-прежнему приветствую любые ответы на этот (очень активный) пост, в которых приведен любой передовой код для правильной доменной модели.
"I don't want to duplicate all my entities into DTOs simply because I don't need it and it violates DRY, and I also don't want my client application to take a dependency on EntityFramework.dll"
. «Сущности» в жаргоне Entity Framework - это не то же самое, что «сущности», как и в «Модель предметной области»Ответы:
Самый полезный ответ дал Алексей Зимарев, получивший не менее 7 голосов, прежде чем модератор переместил его в комментарий под моим первоначальным вопросом ....
Его ответ:
Я сделал несколько заметок, чтобы подвести итог видео как для моей команды, так и для более подробного ознакомления с этим постом. (Видео длится час, но оно действительно стоит каждой минуты, если у вас есть время. Джимми Богард заслуживает большой похвалы за его объяснения.)
Пожалуйста, не стесняйтесь комментировать любые другие моменты, которые, по вашему мнению, должны быть включены, или если вы считаете, что какие-либо из этих заметок не соответствуют действительности. Пытался цитировать прямо или перефразировать как можно больше.
источник
На ваш вопрос невозможно ответить, потому что ваш пример неверен. В частности, потому что нет поведения. По крайней мере, не в области вашего домена. Примером
AddStatusUpdate
метода является не логика домена, а логика, которая использует этот домен. Такая логика имеет смысл быть внутри какой-то службы, которая обрабатывает внешние запросы.Например, если требовалось, чтобы конкретный рабочий элемент мог иметь только определенные статусы или чтобы он мог иметь только N статусов, то это логика домена и должна быть частью
WorkItem
илиStatusHistory
как метод.Причина вашей путаницы в том, что вы пытаетесь применить руководство к коду, который в нем не нуждается. Доменные модели актуальны, только если у вас много сложной доменной логики. Например. логика, которая работает на самих сущностях и вытекает из требований. Если код предназначен для манипулирования объектами из внешних данных, то это, скорее всего, не доменная логика. Но в тот момент, когда вы получаете много
if
s в зависимости от того, с какими данными и объектами вы работаете, тогда это логика предметной области.Одна из проблем настоящего моделирования предметной области заключается в том, что речь идет об управлении сложными требованиями. И как таковая его истинная сила и преимущества не могут быть продемонстрированы на простом коде. Вам нужны десятки сущностей с тоннами требований, чтобы действительно увидеть преимущества. Опять же, ваш пример слишком прост для доменной модели, чтобы действительно сиять.
И наконец, кое-что, что я хотел бы упомянуть, заключается в том, что настоящую модель предметной области с реальным дизайном ООП было бы действительно трудно сохранить при помощи Entity Framework. Хотя ORM были спроектированы с отображением истинной структуры ООП на реляционные, все еще существует много проблем, и реляционная модель часто просачивается в модель ООП. Даже с nHibernate, который я считаю гораздо более мощным, чем EF, это может быть проблемой.
источник
Ваше предположение о том, что инкапсуляция вашей бизнес-логики, связанной с WorkItem, в «толстый сервис» является неотъемлемым анти-паттерном, который, я бы сказал, не обязательно.
Независимо от вашего мнения об анемичной модели предметной области, стандартные шаблоны и методы, типичные для приложения Line of Business .NET, поддерживают многоуровневый подход на основе транзакций, состоящий из различных компонентов. Они поощряют отделение бизнес-логики от модели предметной области, в частности, для облегчения связи модели общей предметной области между другими компонентами .NET, а также с компонентами в различных технологических стеках или между физическими уровнями.
Одним из примеров этого может быть веб-служба SOAP на основе .NET, которая взаимодействует с клиентским приложением Silverlight, в котором есть библиотека DLL, содержащая простые типы данных. Этот проект сущности домена может быть встроен в сборку .NET или сборку Silverlight, где заинтересованные компоненты Silverlight, имеющие эту DLL, не будут подвергаться поведению объектов, которое может зависеть от компонентов, доступных только службе.
Независимо от вашей позиции в этой дискуссии, это принятая и принятая модель, выдвинутая Microsoft, и, по моему профессиональному мнению, это не неправильный подход, но тогда объектная модель, которая определяет его собственное поведение, также не обязательно является анти-шаблоном. Если вы продолжите эту разработку, лучше всего понять и понять некоторые ограничения и болевые точки, с которыми вы можете столкнуться, если вам нужно интегрироваться с другими компонентами, которые должны видеть вашу модель предметной области. В этом конкретном случае, возможно, вы захотите, чтобы Translator преобразовал вашу модель предметной области стиля в простые объекты данных, которые не предоставляют определенные методы поведения.
источник
Я понимаю, что этот вопрос довольно старый, поэтому этот ответ для потомков. Я хочу ответить конкретным примером вместо основанного на теории.
Инкапсулируйте «изменение статуса рабочего элемента» в
WorkItem
классе следующим образом:Теперь ваш
WorkItem
класс отвечает за поддержание себя в легальном состоянии. Однако реализация довольно слабая. Владелец продукта хочет получить историю всех обновлений статуса, сделанных вWorkItem
.Мы изменим это на что-то вроде этого:
Реализация претерпела радикальные изменения, но вызывающий
ChangeStatus
метод не знает подробностей базовой реализации и не имеет оснований для изменения самого себя.Это пример объекта модели расширенного домена, ИМХО.
источник