Domain-Driven-Design - внешние зависимости в проблеме сущностей

23

Я хотел бы начать Domain-Driven-Design, но есть несколько проблем, которые я хотел бы решить перед началом :)

Давайте представим, что у меня есть Группы и Пользователи, и когда пользователь хочет присоединиться к группе, я вызываю groupsService.AddUserToGroup(group, user)метод. В DDD я должен сделать group.JoinUser(user), что выглядит довольно хорошо.

Проблема возникает, если существуют какие-то правила проверки для добавления пользователя или если при добавлении пользователя в группу необходимо запустить некоторые внешние задачи. Наличие этих задач приведет к тому, что у сущности будут внешние зависимости.

Примером может быть - ограничение, что пользователь может участвовать только в 3 группах макс. Для этого потребуется DB-вызовы из метода group.JoinUser для проверки этого.

Но тот факт, что сущность зависит от некоторых внешних служб / классов, не кажется мне таким хорошим и «естественным».

Как правильно бороться с этим в DDD?

Shaddix
источник

Ответы:

15

Давайте представим, что у меня есть Группы и Пользователи, и когда пользователь хочет присоединиться к группе, я вызываю метод groupsService.AddUserToGroup (group, user). В DDD я должен сделать group.JoinUser (пользователь), который выглядит довольно хорошо.

Но DDD также рекомендует вам использовать (не сохраняющие состояние) сервисы для выполнения задач, если выполняемая задача слишком сложна или не вписывается в модель объекта. Это нормально, чтобы иметь сервисы на уровне домена. Но услуги на уровне домена должны включать только бизнес-логику. Внешние задачи и логика приложения (например, отправка электронной почты), с другой стороны, должны использовать доменную службу на уровне приложений, в которую, например, может входить отдельная (прикладная) служба.

Проблема появляется, если у меня есть некоторые правила проверки для добавления пользователя ...

Правила валидации принадлежат модели домена! Они должны быть заключены в доменные объекты (объекты и т. Д.).

... или некоторые внешние задачи должны быть запущены при добавлении пользователя в группу. Наличие этих задач приведет к тому, что у сущности будут внешние зависимости.

Хотя я не знаю, о какой внешней задаче вы говорите, я предполагаю, что это что-то вроде отправки электронного письма и т. Д. Но на самом деле это не является частью модели вашего домена. Он должен жить на прикладном уровне и передаваться туда imho. У вас может быть служба на уровне приложений, которая работает с доменными службами и объектами для выполнения этих задач.

Но тот факт, что сущность зависит от некоторых внешних служб / классов, не кажется мне таким хорошим и «естественным».

Это неестественно и не должно происходить. Сущность не должна знать о том, что не входит в ее обязанности. Услуги должны использоваться для организации взаимодействия сущностей.

Как правильно бороться с этим в DDD?

В вашем случае отношения должны быть двунаправленными. Вступает ли пользователь в группу или группа принимает пользователя, зависит от вашего домена. Пользователь присоединяется к группе? Или пользователь добавлен в группу? Как это работает в вашем домене?

В любом случае, у вас двунаправленные отношения, и вы можете определить количество групп, к которым пользователь уже принадлежит, в совокупности пользователей. Передача пользователя группе или группы пользователю технически тривиальна после определения ответственного класса.

Затем проверка должна быть выполнена объектом. Все это вызывается службой прикладного уровня, которая также может выполнять технические функции, такие как отправка электронных писем и т. Д.

Однако, если логика проверки действительно сложна, лучшим решением может быть служба домена. В этом случае инкапсулируйте бизнес-правила, а затем вызовите их из уровня приложения.

сокол
источник
Но если мы перемещаем так много логики за пределы сущности, что должно быть внутри?
SiberianGuy
Прямые обязанности субъекта! Если вы можете сказать «пользователь может присоединиться к группе», например, это является обязанностью пользователя. Иногда вам приходится принимать компромиссные решения по техническим причинам. Я не большой поклонник двунаправленных отношений, но иногда это лучше всего подходит для модели. Так что слушайте внимательно, когда говорите о домене. «Сущность делает ...» «Сущность может ...» Когда вы слышите такие предложения, то эти операции, скорее всего, принадлежат сущности.
Сокол
Кроме того, вы знаете, что вам нужен сервис, когда два или более не связанных между собой объекта должны участвовать в задании для выполнения чего-либо.
Сокол
1
Спасибо за ваш ответ, Сокол! Кстати, я всегда пытался использовать службы без сохранения состояния, поэтому я на шаг ближе к DDD :) Скажем, в домене эта операция UserJoinsToGroup принадлежит Group. Проблема в том, что для проверки этой операции мне нужно знать, в каких группах этот пользователь уже участвует (чтобы запретить операцию, если она уже> 3). Чтобы знать, что мне нужно запросить базу данных. Как я могу сделать это из группы компаний? У меня есть еще несколько примеров, когда мне нужно прикасаться к БД в операциях, которые, естественно, должны принадлежать сущности (я опубликую их при необходимости :))
Shaddix
2
Хорошо, если я подумаю об этом: как насчет организации GroupMembership? Он может быть построен фабрикой, и эта фабрика может получить доступ к хранилищам. Это было бы хорошим DDD и заключает в себе создание членства. Фабрика может получить доступ к репозиториям, создает членство и затем добавляет его к пользователю и группе соответственно. Этот новый объект также может инкапсулировать привилегии. Может быть, это хорошая идея.
Сокол
3

Я бы подошел к проблеме проверки так: создайте доменную службу с именем MembershipService:

class MembershipService : IMembershipService
{
   public MembershipService(IGroupRepository groupRepository)
   { 
     _groupRepository = groupRepository;
   }
   public int NumberOfGroupsAssignedTo(UserId userId)
   {
        return _groupsRepository.NumberOfGroupsAssignedTo(userId);
   }
}

Субъекту Группы необходимо ввести IMemberShipService. Это можно сделать на уровне класса или на уровне метода. Предположим, мы делаем это на уровне метода.

class Group{

   public void JoinUser(User user, IMembershipService membershipService)
   {
       if(membershipService.NumberOfGroupsAssignedTo(user.UserId) >= 3)
         throw new BusinessException("User assigned to more than 3 groups. Cannot proceed");

       // do some more stuff
   }
}

Служба приложения: GroupServiceможет быть внедрена с IMemberShipServiceпомощью конструктора, который затем может быть передан JoinUserметоду Groupкласса.

Эклавя Гупта
источник
1
Возможно, вы захотите рассмотреть возможность форматирования исходного кода в своем посте для удобства чтения
Бенни