DDD / CQRS / ES - Как и где внедрить охрану - PullRequest
0 голосов
/ 01 февраля 2019

Доброе утро,

У меня есть модель, в которой Пользователь AR имеет определенную UserRole (администратор, посредник или клиент).Для этого AR есть несколько средств защиты, которые я бы реализовал:

  • Администратор не может иметь другого менеджера, кроме него
  • Торговый посредник не может иметь менеджера, кроме администратора
  • У клиента не может быть менеджера, кроме реселлера или клиента (случай субсчета)

Допустим, я хочу зарегистрировать нового пользователя .Процесс будет выглядеть следующим образом:

RegisterUser обработчик запроса -> RegisterUser Команда -> RegisterUser обработчик команды -> User-> register (...) method -> UserWasRegistered Событие домена

Как и где я должен реализовать средства защиты для точной проверки моего User AR?Прямо сейчас у меня есть что-то, что выглядит следующим образом:

namespace vendor\Domain\Model;

class User
{
    public static function register(
        UserId $userId,
        User $manager,
        UserName $name,
        UserPassword $password,
        UserEmail $email,
        UserRole $role
    ): User
    {
        switch($role) {
            case UserRole::ADMINISTRATOR():
                if(!$userId->equals($manager->userId)) {
                    throw new \InvalidArgumentException('An administrator cannot have a manager other than himself');
                }
                break;
            case UserRole::RESELLER():
                if(!$manager->role->equals(UserRole::ADMINISTRATOR())) {
                    throw new \InvalidArgumentException('A reseller cannot have a manager other than an administrator');
                }
                break;
            case UserRole::CLIENT():
                // TODO: This is a bit more complicated as the outer client should have a reseller has manager
                if(!$manager->role->equals(UserRole::RESELLER()) && !$manager->role->equals(UserRole::Client())) {
                    throw new \InvalidArgumentException('A client cannot have a manager other than a reseller or client');
                }
        }

        $newUser = new static();
        $newUser->recordThat(UserWasRegistered::withData($userId, $manager, $name, $password, $email, $role, UserStatus::REGISTERED()));

        return $newUser;
    }
}

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

И последнее, я бы предпочел передать UserId VO, а не Пользователь AR для свойства менеджера, поэтому я думаю, что охранники не должны быть помещены в Пользователь AR.

Ваш совет был бы очень признателен.

Ответы [ 2 ]

0 голосов
/ 01 февраля 2019

Как вы видите, охранники в самой модели, что я считаю плохим.Мне интересно, стоит ли мне помещать эти средства защиты во внешние валидаторы или в обработчик команд.

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

Я бы предпочел передать объект значения UserId, а не агрегат User для свойства менеджера.

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

Когда речь идет о предоставлении агрегированной информации извне, существует две основные стратегии:

  1. Поиск данных перед вызовом домена (например, в службе приложений)

    • Пример (псевдокод):

      Application {
          register(userId, managerId, ...) {
              managerUser = userRepository.userOfId(userId);
              //Manager is a value object
              manager = new Manager(managerUser.id(), managerUser.role());
              registeredUser = User.register(userId, manager, ...);
              ...
          }
      }
      
    • Когда использовать? Это самый стандартный и самый чистый подход (агрегаты никогда не выполняют косвенный ввод-вывод).Я бы всегда сначала рассматривал эту стратегию.

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

  2. Passдоменная служба для домена, которую она может использовать для поиска данных самостоятельно.

    • Пример (псевдокод):

      interface RoleLookupService {
          bool userInRole(userId, role);
      }
      
      Application { 
          register(userId, managerId, ...) {
              var registeredUser = User.register(userId, managerId, roleLookupService, ...);
              ...
          }
      }
      
    • Когда использовать? Я бы рассмотрел этот подход, когдасама логика поиска достаточно сложна, чтобы заботиться о ее инкапсуляции в домене, а не просачивать ее на уровень приложения.Однако, если вы хотите сохранить чистоту агрегатов, вы также можете извлечь весь процесс создания на фабрике (служба домена), на которую будет опираться прикладной уровень.

    • Что Вы всегда должны помнить о Принципе разделения интерфейса здесь и избегать больших контрактов, таких как IUserRepository, когда единственное, что нужно посмотреть - это роль пользователя или нет.Кроме того, этот подход не считается «чистым», поскольку агрегаты могут выполнять косвенный ввод-вывод.Зависимость службы может также потребовать больше работы для моделирования, чем зависимость данных для модульных тестов.

Рефакторинг исходного примера

  • Избегатьпередача другого экземпляра AR
  • Явно смоделируйте политику политики надзора как первоклассного гражданина, связанного с определенной ролью.Обратите внимание, что вы можете использовать любые варианты моделирования, где правило связано с ролью.Я не обязательно удовлетворен языком в примере, но вы поймете идею.

    interface SupervisionPolicy {
        bool isSatisfiedBy(Manager manager);
    }
    
    enum Role {
        private SupervisionPolicy supervisionPolicy;
    
        public SupervisionPolicy supervisionPolicy() { return supervisionPolicy; }
    
        ...
    }
    
    
    class User {
        public User(UserId userId, Manager manager, Role role, ...) {
            //Could also have role.supervisionPolicy().assertSatisfiedBy(manager, 'message') which throws if not satsified
            if (!role.supervisionPolicy().isSatisfiedBy(manager)) {
                throw …;
            }
        }
    }
    
0 голосов
/ 01 февраля 2019

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

Это обычно означает, чтоОбработчик команд будет отвечать за сантехнику (загрузка данных из базы данных, хранение изменений в базе данных) и делегировать модели домена работу по вычислению последствий запроса пользователя.

Таким образом, «охранники»"обычно реализуется в модели предметной области.

И последнее, я бы предпочел передать идентификатор пользователя, а не пользователя для свойства менеджера, поэтому я считаю, что охрану не следует ставитьв модели User.

Это нормально - когда модели домена требуется информация, которая не является локальной, вы обычно либо просматриваете эту информацию и передаете ее, либо передаете возможность поискаинформация.

Таким образом, в этом случае вы можете передавать "доменную службу", котораязнает, как искать UserRole с заданным UserId.

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

Мое сильное предпочтение заключается в том, что службы передаются в качестве аргументов нужным им методам и не являются частью * реализации.Таким образом, сущности в доменной модели содержат данные , а соавторы предоставляются по запросу.

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

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

...