RDM против ADM (еще раз) или где есть середина?
Проведя бесконечные часы в Интернете (и в книгах), пытаясь прийти к заключению по этому вопросу, просматривая точки зрения многих людей и различные аспекты, пытаясь взвесить все за и против, я решил опубликовать некоторые ключевые вопросы, которые я надеюсь, что некоторые люди умнее меня ответят:)
Я прочитал статьи Мартина Фаулера об ADM (модель анемичного домена, как он его называет), а также книги, и я знаю о DDD Эрика Эвана (доменно-управляемый дизайн). Они очень респектабельные, опытные архитекторы, и они проделали необычайную работу, собрав все эти знания в своих книгах и статьях, однако (и я знаю, что это почти невозможно сделать, потому что это так во всех печатных СМИ), их примеры как правило, очень четкий, что хорошо для объяснения концепции, но, к сожалению, их трудно использовать в реальной жизни.
Здесь я быстро объясню несколько случаев, в которых я очень заинтересован в вашем решении (RDM / ADM+TS(Service)), давайте предположим, что контейнер IoC выполняет подключение (хотя это в основном не имеет значения):
Случай I/1:
ЗАДАЧА: Оформление заказа
ADM+TS:
ТРЕБОВАНИЯ:
Order oject - data (set + get) (DTO-подобный "пакет данных")
OrderService - операции (TS-подобные операции над объектом-сущностью)
RDM:
ТРЕБОВАНИЯ:
Объект заказа - данные и функциональность (Rich)
Случай I/2:
ЗАДАЧА: оформить заказ и отправить по электронной почте после него
ADM + TS
ТРЕБОВАНИЯ:
Order oject - data (set + get) (DTO-подобный "пакет данных")
OrderService - операции (TS-подобные операции над объектом-сущностью)
EmailService - отправлять электронные письма
(необязательно) OrderServiceEmailDecorator - чтобы отделить задачу от фактического размещения заказа и отправки электронного письма
КОММЕНТАРИИ:
Решения:
а. Используйте существующий OrderService и добавьте к нему электронную почту, в этом случае OrderService зависит от EmailService
б. Разделите заботу на декоратор, который мы можем подключить вместе с сервисом в IoC и использовать по требованию в зависимости от того, хотим мы этого или нет
RDM:
ТРЕБОВАНИЯ:
Объект заказа - данные и функциональность (Rich)
? EmailService - отправлять электронные письма
? EventHandler - ловить события
КОММЕНТАРИИ:
Ну, в этом случае люди обычно рекомендуют следующие вещи:
а. "Вставьте ваши зависимости в доменный слой": это сделало бы доменный слой очень тяжелым и полным зависимостей повсюду.
б. "Передать службу вместе с вызовом place(...)": это будет приводить к постоянному изменению сигнатуры функции, поскольку в нее входит все больше и больше зависимостей.
с. "Создайте событие, если значительная операция была завершена": даже самые сильные сторонники RDM говорят, что постоянство не должно быть непосредственно в модели предметной области, что означает, что мы инициируем событие здесь, однако операция не была выполнена полностью (сохранялась). поэтому мы можем отправить электронное письмо до того, как это будет сделано. Мы могли бы сказать, что электронная почта может не получиться, поэтому она не идеальна в любом случае, но я думаю, что на самом деле размещение заказа является основной операцией, отправка электронной почты - это просто уведомление, а также, что можно повторить, плюс вы получите на экране уведомления и т. д. Здесь вы понимаете, что размещение заказа не зависит от возможности отправить уведомление по электронной почте, но вы определенно не хотите отправлять электронное письмо, если сохранение заказа не удается. НО некоторые могут сказать, что такие события возникают в репозитории, где он сохраняется, это больше похоже на то, как бы эти события не распространялись.
Дело I/3:
ЗАДАЧА: Заказы могут быть размещены оптом, и мы хотим отправить только одно уведомление по электронной почте со всеми заказами в нем (Пожалуйста, не начинайте комментировать, как это просто элементы в одном заказе, это пример).
ADM + TS
ТРЕБОВАНИЯ:
Order oject - data (set + get) (DTO-подобный "пакет данных")
OrderService - операции (TS-подобные операции над объектом-сущностью)
BulkOrderService - будет зависеть от OrderService (без оформления)
EmailService - отправлять электронные письма
BulkOrderServiceEmailDecorator - зависит от EmailService для отправки агрегированной электронной почты
КОММЕНТАРИИ:
Вместо использования оформленного OrderService мы используем (Decorated) BulkOrderService
RDM:
ТРЕБОВАНИЯ:
Объект заказа - данные и функциональность (Rich)
? EmailService - отправлять электронные письма
? EventHandler - ловить события
КОММЕНТАРИИ: Наш объект предметной области становится немного сложнее, мы не можем добавить к нему.bulkPlace(), потому что, очевидно, я хочу разместить несколько заказов в одном контексте, так что логика должна существовать на один уровень вверх, скажем, на уровне контроллера, и вызывать каждое место () для каждого заказа, в этом случае: Продолжая сверху (на основе решений I/2 RDM):
а. ("Внедренные зависимости") Как мы можем обойтись с отправкой электронной почты здесь? Теперь нам нужны.placeWithEmailAndAnyOtherDependency.. и.placeWithout...? Вы не можете точно украшать доменные объекты, чтобы быть справедливым
б. ("Пропустить сервис"). Теперь здесь вы можете сделать это, если вы передадите ноль вместо сервиса, он не будет отправлять электронное письмо (но это кажется хитрым)
с. ("Поднять событие") Это проблема, теперь, когда мы связали отправку электронной почты с этим событием, и мы хотим повторно использовать вызов.place(), даже при массовых заказах будет отправлено несколько электронных писем, если мы не можем каким-либо образом отсоединить его (не может действительно украсить хранилище)
Теперь некоторые из этих проблем, вероятно, могут быть решены с помощью AOP вместо декораторов, но все же это кажется хакерским.
Дело I/4:
ЗАДАЧА: Теперь у нас есть несколько точек входа, учитывая, что мы хотим иметь возможность "планировать" повторяющиеся массовые заказы от нашего планировщика, но также хотим сохранить эту функцию непосредственно на нашем веб-сайте. (Или я могу просто сказать, что у нас есть консольный клиент, а также веб-клиент, но дело в том, что наши веб-контроллеры не будут выполнять эту работу, во всяком случае, напрямую)
ADM + TS
ТРЕБОВАНИЯ (без изменений):
Order oject - data (set + get) (DTO-подобный "пакет данных")
OrderService - операции (TS-подобные операции над объектом-сущностью)
BulkOrderService - будет зависеть от OrderService (без оформления)
EmailService - отправлять электронные письма
BulkOrderServiceEmailDecorator - зависит от EmailService для отправки агрегированной электронной почты
КОММЕНТАРИИ: Вместо использования оформленного OrderService мы используем (Decorated) BulkOrderService - по сути, ничего не меняется
RDM:
ТРЕБОВАНИЯ:
Объект заказа - данные и функциональность (Rich)
? EmailService - отправлять электронные письма
? EventHandler - ловить события
КОММЕНТАРИИ: Зависит от того, что мы делали в I/2, те же проблемы, которые I / 3 все еще применяли, но в довершение всего, мы больше не можем использовать наш контроллер для циклического выполнения заказов или, если мы делаем это, превращается в нечто вроде TS, и мы вернулись к аналогичной многоуровневой архитектуре, которую мы получаем с ADM + TS
Итак, моя главная проблема в том, что я не смог найти определенного, подходящего решения в RDM для такой простой проблемы, как я сам, даже после того, как читатели и пользователи Google рекомендуют разные вещи, которые хороши, чтобы решить одну вещь, но кровоточат с другой стороны, в то время как решения ADM + TS чувствуют себя более гибкими, работая с ними. (Не говоря уже о том, что вам не нужны DTO-ы, потому что ваш ADM - это, по сути, ваш DTO, который вы можете передать событию в представление - так что никаких преобразований не требуется)
Если у вас есть мнение о том, как (постепенно) обрабатывать случаи I / 2 и / 3 с помощью RDM так, как вам удобно, оставьте комментарий, однако, если вы это сделаете, предоставьте и ответьте на все вопросы (все 4 или, по крайней мере, последние 3, так как 1 не является проблемой)! Не только те, на которые у вас есть удобный ответ (например, половина задач и т. Д.)
Спасибо
ОБНОВЛЕНИЕ: видя некоторые ответы, я, вероятно, должен был выбрать другую "сущность", чем знаменитый Орден для этого упражнения (я просто хотел выбрать знакомую). В любом случае, в качестве дополнения, попробуйте представить, что Варианты I/2, I/3, I/4 НЕ были начальными требованиями, они вроде органично развивались. Эти требования были добавлены шаг за шагом. Итак, сначала вам сказали посылать электронную почту всякий раз, когда есть заказ, теперь, если вы каким-либо образом связали их вместе, у вас возникнет проблема, когда я / 3 выполню массовый заказ. Даже если вы просто поместите электронное письмо в шину сообщений, а оно еще не было отправлено, что вы будете делать навалом? Затем вы положили сообщение на шину, а затем удалить его / делать очистку? Или с любым другим действием, которое на основе I / 2 должно быть запущено, но на основе I / 3, больше не применимо, просто сделать их в любом случае, а затем отменить их? Это не звучит правильно
2 ответа
Дело I/2
Чтобы решить проблему преждевременной отправки электронных писем в ответ на неудачные события в домене, посмотрите здесь.
Дело 1/3
Если размещение оптовых заказов является вариантом использования, сделайте это явным образом в домене. Независимо от того, используете ли вы модель домена или сценарий транзакции, у вас все еще есть служба приложений. В свою очередь, эта прикладная служба будет иметь метод, реализующий сценарий использования массового размещения заказов. Что касается модели предметной области, вы можете создать агрегат BulkOrder, с которым можно дополнительно связать существующие заказы.
В целом, я думаю, что ваша идея богатой доменной модели слишком строгая. Даже с богатой моделью домена у вас все еще есть координирующие сервисы приложений. Кроме того, рефакторинг в модель предметной области часто обеспечивает глубокое понимание самого домена, заставляя неявные концепции стать явными.
Заказ - это всегда документ, указывающий, какие продукты и количества заказал клиент. По этой причине он неизменен, поэтому мы не можем говорить о слишком богатом поведении. Но важно, что Орден является бизнес-концепцией, ему не нужно иметь 20 методов. Для цен, итогов и налогов существует бизнес-концепция Счета-фактуры, прикрепленная к Заказу.
Дело I/1, I/2
Я не вижу проблемы в использовании событий домена. Допустим, заказ был отправлен
class ManageOrderHandler:IExecute<CreateOrder>
{
public void Execute(CreateOrder cmd)
{
Order order=someFactory.CreateOrder(cmd); //not important
_repository.Save(order);
_bus.Publish(new OrderSubmitted());
}
}
Событие публикуется только после того, как заказ был сохранен.
class Notifier:ISubscribeTo<OrderSubmitted>
{
NotifyService _service;//constructor injected
public void Handle(OrderSubmitted evnt)
{
_service.Notify(/* relevant parameters */);
}
}
class NotifyService
{
public NotifyService(ISendEmails emailNotifier, ISendSms smsNotifier /* etc */)
{}
public void Notify(/* arguments */)
{
// depending on settings and the arguments send email and/or sms and/or others
}
}
Конечно, NotifyService (извините за плохое именование) может быть непосредственно обработчиком OrderSubmitted, но сейчас я предпочитаю, чтобы вещи были настолько развязаны, насколько это возможно.
Дело I/3, I/4
Вроде то же самое, но с поправкой. Репозиторий принимает более одного Заказа, поэтому все заказы будут считаться транзакцией. Опубликованным событием будет BulkOrdersSubmitted, содержащее соответствующие данные. NotifyService не изменяется.
Справедливости ради, я гораздо больше беспокоюсь о том, чтобы правильно моделировать Домен. Использование архитектуры, управляемой сообщениями, позволяет вам писать вещи не связанными, и их гораздо легче настраивать или внедрять новые функции. Но для этого вам действительно нужно получить правильное моделирование домена.