Не имея проблем с реализацией реальной логики в домене DDD
Несмотря на то, что мы долгое время изучали Domain Driven Design
, все еще есть некоторые основы, которые я просто выясняю.
Кажется, что каждый раз, когда я пытаюсь создать богатый domain layer
, мне все еще нужно много Domain Services
или толстый Application Layer
, и я получаю кучу сублимированных объектов домена без реальных логики в них, помимо "GetTotalAmount" и тому подобного. Ключевая проблема заключается в том, что сущности не знают о внешнем материале, и плохая практика заключается в том, чтобы вводить что-либо в сущности.
Позвольте привести несколько примеров:
1. Пользователь подписывается на услугу. Пользователь сохраняется в базе данных, файл создается и сохраняется (необходим для учетной записи пользователя), и отправляется сообщение с подтверждением.
Пример с электронным письмом с подтверждением подробно обсуждался в других потоках, но без реального вывода. Некоторые предлагают положить логику в application service
, которая получает EmailService
и FileService
, введенные из infrastructure layer
. Но тогда у меня была бы бизнес-логика вне домена, не так ли? Другие предлагают создать domain service
, который получает infrastructure services
, но в этом случае мне нужно будет иметь интерфейсы infrastructure services
внутри domain layer
(IEmailService
и IFileService
), который не выглядит слишком хорошо (потому что domain layer
не может ссылаться на infrastructure layer
). Другие предлагают реализовать События домена Udi Dahan, а затем подписываются на эти события в службе EmailService и FileService. Но это похоже на очень неудачную реализацию - и что произойдет, если службы потерпят неудачу? Пожалуйста, дайте мне знать, что вы считаете правильным решением здесь.
2. Песня приобретается в магазине цифровой музыки. Корзина покупок опустела. Покупка сохраняется. Вызывается служба оплаты. Подтверждено подтверждение по электронной почте.
Хорошо, это может быть связано с первым примером. Вопрос в том, кто отвечает за организацию этой транзакции? Конечно, я мог бы разместить все в контроллере MVC с помощью инъекционных услуг. Но если я хочу реального DDD, вся бизнес-логика должна быть в домене. Но какой объект должен иметь метод "Покупка"? Song.Purchase()
? Order.Purchase()
? OrderProcessor.Purchase()
(услуга домена)? ShoppingCartService.Purchase()
(служба приложения?)
Это случай, когда мне очень сложно использовать настоящую бизнес-логику внутри объектов домена. Если это не хорошая практика, чтобы что-то вводить в сущности, как они могут делать другие вещи, кроме проверки своего собственного состояния (и его совокупности)?
Я надеюсь, что эти примеры достаточно ясны, чтобы показать проблемы, с которыми я имею дело.
Ответы
Ответ 1
Пользователь подписывается на услугу. Пользователь сохраняется в базы данных, файл создается и сохраняется (необходимо для учетной записи пользователя), и отправляется электронное письмо с подтверждением.
Здесь вы можете применить Принцип инверсии зависимостей. Определите интерфейс домена следующим образом:
void ICanSendConfirmationEmail(EmailAddress address, ...)
или
void ICanNotifyUserOfSuccessfulRegistration(EmailAddress address, ...)
Интерфейс может использоваться другими классами домена. Внедрите этот интерфейс в инфраструктурный уровень, используя реальные классы SMTP. Внедрить эту реализацию при запуске приложения. Таким образом, вы заявили о намерениях бизнеса в коде домена, и ваша логика домена не имеет прямой ссылки на инфраструктуру SMTP. Ключ здесь - это имя интерфейса, оно должно основываться на Ubiquitous Language.
Песня приобретается в магазине цифровой музыки. Корзина опорожняется. Покупка сохраняется. Вызывается служба оплаты. Подтверждено подтверждение по электронной почте. Хорошо, это может быть связано с первым примером. Вопрос в том, кто отвечает за организацию этой транзакции?
Используйте лучшие практики ООП для назначения обязанностей (GRASP и SOLID). Тестирование модулей и рефакторинг даст вам обратную связь с дизайном. Сама оркестровка может быть частью тонкого уровня приложения. Из DDD Layered Architecture:
Уровень приложения: определяет задания, которые программное обеспечение должно выполнять, и направляет выразительные объекты домена для решения проблем. Задачи этого слоя несет за собой значимые для бизнеса или необходимые для взаимодействие с прикладными уровнями других систем.
Этот слой остается тонким. Он не содержит правил ведения бизнеса или знания, но только координирует задачи, а делегаты совместное использование объектов домена в следующем слое. Это не имеют состояние, отражающее деловую ситуацию, но оно может иметь состояние который отражает ход выполнения задачи для пользователя или программы.
Ответ 2
Ответ Димитрия указывает на некоторые хорошие вещи, которые нужно искать. Часто/легко вы оказываетесь в своем сценарии, с перетаскиванием данных с db до GUI через разные слои.
Я был вдохновлен простым советом Джимми Нильсона "Объекты Value, объекты Value и больше объектов Value". Часто люди склонны уделять много внимания существительным и моделировать их как сущность. Естественно, у вас часто возникают проблемы с поиском поведения DDD. Глаголы легче связывать с поведением. Хорошо, что эти глаголы появляются в вашем домене как объекты Value.
Некоторые рекомендации, которые я использую для себя при попытке разработки домена (нужно сказать, что для создания богатого домена требуется время, часто несколько итераций рефакторинга...):
- Свернуть свойства (get/set)
- Использовать объекты значений столько, сколько вы можете
- Выставляйте как можно меньше. Сделайте интуитивные методы агрегирования доменов.
Не забывайте, что ваш домен может быть богат, выполнив проверку. Это только ваш домен, который знает, как совершить покупку, и что нужно.
Ваш домен также должен отвечать за проверку, когда ваши сущности совершают переход из одного состояния в другое состояние (проверки рабочего потока).
Я приведу несколько примеров:
Вот статья, которую я написал в своем блоге относительно вашей проблемы о анемичном домене http://magnusbackeus.wordpress.com/2011/05/31/preventing-anemic-domain-model-where-is-my-model-behaviour/
Я также могу порекомендовать статью блога Jimmy Bogard о проверке сущности и использовании шаблона Validator вместе с методами расширения. Это дает вам свободу проверять инфраструктурные вещи, не загрязняя ваш домен:
http://lostechies.com/jimmybogard/2007/10/24/entity-validation-with-visitors-and-extension-methods/
Я использую Udi Domain Events с большим успехом. Вы также можете сделать их асинхронными, если считаете, что ваша служба может выйти из строя. Вы также переносите его в транзакцию (используя инфраструктуру NServiceBus).
В вашем первом примере (просто мозговой штурм теперь, чтобы наши мысли больше думали о объектах ценности).
- Служба приложений
MusicService.AddSubscriber(User newUser)
получает вызов от ведущего/контроллера/WCF с новым пользователем.
Сервис уже получил IUserRepository
и IMusicServiceRepository
, введенные в ctor.
- Музыкальный сервис "Spotify" загружается через IMusicServiceRepository
- Объект
musicService.SignUp(MusicServiceSubscriber newSubsriber)
принимает объект Value MusicServiceSubscriber
.
Этот объект Value должен принимать Пользовательские и другие обязательные объекты в ctor
(объекты значения неизменяемы). Здесь вы также можете разместить логику/поведение, например handle subscriptionId и т.д.
- Что также делает метод SignUp, он запускает событие домена
NewSubscriberAddedToMusicService
.
Он попадает на EventHandler HandleNewSubscriberAddedToMusicServiceEvent
, который получил IFileService
и IEmailService
, введенный в него ctor. Эта реализация обработчика находится в слое Application Service, но событие контролируется Domain и MusicService.SignUp
. Это означает, что домен находится под контролем. Eventhandler создает файл и отправляет электронную почту.
Вы можете сохранить пользователя через обработчик событий или сделать метод MusicService.AddSubscriber(...)
этим. Оба будут делать это через IUserRepository
, но это вопрос вкуса и, возможно, как он будет отражать фактическую область.
Наконец... Надеюсь, вы поймете что-то из вышеперечисленного... во всяком случае. Самое главное - начать добавлять методы "глаголов" к сущностям и создавать совлокальные действия. Вы также можете иметь объект в своем домене, который не сохраняется, они существуют только для посредничества между несколькими объектами домена и могут содержать алгоритмы и т.д.
Ответ 3
Большая часть ваших запросов связана с объектно-ориентированным дизайном и назначением ответственности, вы можете придумать Шаблоны GRASP и Это, вы можете воспользоваться объектно-ориентированными книгами по дизайну, порекомендовать следующие
Применение UML и шаблонов