Вложенные Commands и создание сущностей

Обсуждаем, как правильно строить приложения
Ответить
Аватара пользователя
BrusSENS
Сообщения: 496
Зарегистрирован: 2012.07.26, 06:51
Откуда: Новороссийск
Контактная информация:

Вложенные Commands и создание сущностей

Сообщение BrusSENS » 2017.09.12, 03:09

Привет всем. Появился вопрос.
Есть у нас команда UserSignUpCommand и UserSignUpHandler. Хендлер следующего вида

Код: Выделить всё

class SignUpHandler implements CommandHandler
{
    protected $userRepository;
    protected $eventDispatcher;
    protected $userFactory;

    public function __construct(
        UserRepository $userRepository,
        EventDispatcher $eventDispatcher,
        UserFactory $userFactory
    )
    {
        $this->userRepository = $userRepository;
        $this->userFactory = $userFactory;
        $this->eventDispatcher = $eventDispatcher;
    }

    /**
     * @param $command SignUpCommand
     */
    public function handle($command)
    {
        $user = $this->userFactory->signUp();
        $this->userRepository->add($user);
        $this->eventDispatcher->dispatch($user->releaseEvents());
    }
}
Как видно, сущность создаётся через фабрику.
Собственно вопросы:
1. Создание через фабрику - по сути обычное создание сущности через гидрацию, т.к. в конструкторе выкидывается событие создания.
Думал в сторону создания сущности через конструктор, а через нестатичные методы вроде create() и signUp() уже просто выбрасывать нужные события. Насколько верный такой подход?
2. Насколько верно создавать сущность через "именованные конструкторы"?
3. Каким образом будет верно реализовать генерацию и отправки токена для подтверждения E-mail? Пока думаю в сторону запуска внутри хендлера дополнительных команд CreateEmailConfirmTokenCommand, и внутри этой команды запускать ещё и SendEmailConfirmTokenCommand, насколько верным будет такой подход?
4. Может ли команда принимать сущность в качестве аргумента?
5. Насколько верно в фабрике переводить сущность в состояние массива (например $userFactory->toState(User $user))? Или всё таки писать для конвертации состояний отдельный маппер?
Native Web - небольшой блог о веб разработке (временно на ремонте)
Режим обслуживания сайта для Yii 2.x.x

Аватара пользователя
ElisDN
Сообщения: 5026
Зарегистрирован: 2012.10.07, 10:24
Контактная информация:

Re: Вложенные Commands и создание сущностей

Сообщение ElisDN » 2017.09.12, 09:01

1,2. Гидрацию используйте только при извлечении из БД в репозитории. В домене же используйте всё напрямую через обычные или именованные конструкторы:

Код: Выделить всё

$user = User::requestSignUp(
    $command->username,
    $command->email,
    $this->passwordHasher->hash($command->password),
    $this->confirmTokenizer->generate()
);
Если не нравятся статические методы, то можно заменить на обычные как viewtopic.php?f=34&t=36725&start=160#p192900

Так что без фабрики здесь можно обойтись.

3. Команда - это как экшен: одна на весь процесс. Как видим, здесь мы сразу передаём $this->confirmTokenizer->generate(). А потом к событию new UserSignedUp($this) в обработчике UserSignedUpListener отправляем письмо.

4. Команду мы создаём в контроллере. Сущностей там нет.

5. Фабрика создаёт объекты. Если это как-то связано с созданием, то в неё.

Аватара пользователя
BrusSENS
Сообщения: 496
Зарегистрирован: 2012.07.26, 06:51
Откуда: Новороссийск
Контактная информация:

Re: Вложенные Commands и создание сущностей

Сообщение BrusSENS » 2017.09.12, 14:46

ElisDN писал(а):
2017.09.12, 09:01
Гидрацию используйте только при извлечении из БД в репозитории
А как же без гидрации тогда извлекать свойства сущности? У User есть свойство passwordHash, но по логике его нельзя получить через геттер. Получается, что только гидрация.
ElisDN писал(а):
2017.09.12, 09:01
Если не нравятся статические методы, то можно заменить на обычные
Думаю, что лучше комбинировать. Для чего-то именованные конструкторы, для чего-то простые нестатичные методы.
ElisDN писал(а):
2017.09.12, 09:01
Команда - это как экшен: одна на весь процесс. Как видим, здесь мы сразу передаём $this->confirmTokenizer->generate(). А потом к событию new UserSignedUp($this) в обработчике UserSignedUpListener отправляем письмо.
Это понятно, но если у нас регистрация пользователя отличается от создания тем, что мы можем указывать, отправлять E-mail или нет, как поступить в таком случае? Кидать событие внутри хендлера?
ElisDN писал(а):
2017.09.12, 09:01
Команду мы создаём в контроллере. Сущностей там нет.
Что то не подумал об IdentityMap. Точно. Запроса второго не будет в UpdateUserHandler.
Native Web - небольшой блог о веб разработке (временно на ремонте)
Режим обслуживания сайта для Yii 2.x.x

zelenin
Сообщения: 10596
Зарегистрирован: 2013.04.20, 11:30

Re: Вложенные Commands и создание сущностей

Сообщение zelenin » 2017.09.12, 15:35

BrusSENS писал(а):
2017.09.12, 14:46
А как же без гидрации тогда извлекать свойства сущности? У User есть свойство passwordHash, но по логике его нельзя получить через геттер. Получается, что только гидрация.
почему мы его не можем получить через геттер?

Аватара пользователя
BrusSENS
Сообщения: 496
Зарегистрирован: 2012.07.26, 06:51
Откуда: Новороссийск
Контактная информация:

Re: Вложенные Commands и создание сущностей

Сообщение BrusSENS » 2017.09.12, 17:41

zelenin писал(а):
2017.09.12, 15:35
почему мы его не можем получить через геттер?
Так а зачем давать возможность получить хэш пароля? Для смены пароля и проверки соответствия есть методы changePassword() и isPasswordRelevant(). Разве мы должны давать прямой доступ к тем или иным хэшам?
Native Web - небольшой блог о веб разработке (временно на ремонте)
Режим обслуживания сайта для Yii 2.x.x

zelenin
Сообщения: 10596
Зарегистрирован: 2013.04.20, 11:30

Re: Вложенные Commands и создание сущностей

Сообщение zelenin » 2017.09.12, 18:02

BrusSENS писал(а):
2017.09.12, 17:41
zelenin писал(а):
2017.09.12, 15:35
почему мы его не можем получить через геттер?
Так а зачем давать возможность получить хэш пароля? Для смены пароля и проверки соответствия есть методы changePassword() и isPasswordRelevant(). Разве мы должны давать прямой доступ к тем или иным хэшам?
так, логично.

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

И вот учитывая все это наш password_hash сохранит и восстановит гидратор.

Аватара пользователя
BrusSENS
Сообщения: 496
Зарегистрирован: 2012.07.26, 06:51
Откуда: Новороссийск
Контактная информация:

Re: Вложенные Commands и создание сущностей

Сообщение BrusSENS » 2017.09.12, 18:47

zelenin писал(а):
2017.09.12, 18:02
И вот учитывая все это наш password_hash сохранит и восстановит гидратор.
Вот об этом я и говорю.
Дмитрий написал, что
ElisDN писал(а):
2017.09.12, 09:01
Гидрацию используйте только при извлечении из БД в репозитории
Потому и говорю, что гидрация может понадобиться не только при восстановлении объекта.
Native Web - небольшой блог о веб разработке (временно на ремонте)
Режим обслуживания сайта для Yii 2.x.x

Аватара пользователя
BrusSENS
Сообщения: 496
Зарегистрирован: 2012.07.26, 06:51
Откуда: Новороссийск
Контактная информация:

Re: Вложенные Commands и создание сущностей

Сообщение BrusSENS » 2017.09.12, 18:51

Zelenin, вот интересует в рамках данной темы то, как же всё таки реализовывать, например отправку E-mail сообщений, если, например, в админке у меня есть возможность указать флаг, отправлять E-mail новому пользователю, или нет? Разные команды? Последовательное исполнение нескольких команд в зависимости от флага? Какой вариант решения будет лучше?
UPD: ещё вопрос: насколько верно назвать методы обращения и восстановления объекта как serialize() и unserialize() согласно UL?
Native Web - небольшой блог о веб разработке (временно на ремонте)
Режим обслуживания сайта для Yii 2.x.x

zelenin
Сообщения: 10596
Зарегистрирован: 2013.04.20, 11:30

Re: Вложенные Commands и создание сущностей

Сообщение zelenin » 2017.09.12, 18:55

BrusSENS писал(а):
2017.09.12, 18:51
Zelenin, вот интересует в рамках данной темы то, как же всё таки реализовывать, например отправку E-mail сообщений, если, например, в админке у меня есть возможность указать флаг, отправлять E-mail новому пользователю, или нет? Разные команды? Последовательное исполнение нескольких команд в зависимости от флага? Какой вариант решения будет лучше?
конкретный кейс какой? что происходит когда отправляем письмо?
BrusSENS писал(а):
2017.09.12, 18:51
UPD: ещё вопрос: насколько верно назвать методы обращения и восстановления объекта как serialize() и unserialize() согласно UL?
это технические моменты, не относящиеся к доменному знанию и UL. Что такое сериализация можно посмотреть в вики.

Аватара пользователя
BrusSENS
Сообщения: 496
Зарегистрирован: 2012.07.26, 06:51
Откуда: Новороссийск
Контактная информация:

Re: Вложенные Commands и создание сущностей

Сообщение BrusSENS » 2017.09.12, 19:06

zelenin писал(а):
2017.09.12, 18:55
конкретный кейс какой? что происходит когда отправляем письмо?

Код: Выделить всё

class CreateHandler implements CommandHandler
{
    protected $repository;
    protected $securityService;
    protected $eventDispatcher;

    public function __construct(
        UserRepository $repository,
        SecurityService $securityService,
        EventDispatcher $eventDispatcher
    )
    {
        $this->userRepository = $userRepository;
        $this->securityService = $securityService;
        $this->eventDispatcher = $eventDispatcher;
    }
    /**
     * @param $command CreateCommand
     */
    public function handle($command)
    {
        $user = $this->createUser($command);
        $this->userRepository->add($user);
        $this->eventDispatcher->dispatch($user->releaseEvents());
        if($command->needSendConfirmation()) {
             // Тут выбрасывать событие, или инициализировать некую SendConfirmationCommand?
        }
    }
    /**
     * @param $command CreateCommand
     * @return User
     */
    protected function createUser($command)
    {
        return User::create(
            $this->userRepository->nextId(),
            $command->getLogin(),
            $command->getEmail(),
            $this->securityService->hashPassword($command->getPassword()),
            $this->securityService->generateUniqueRandomString(),
            $command->getStatus()
        );
    }
}
Native Web - небольшой блог о веб разработке (временно на ремонте)
Режим обслуживания сайта для Yii 2.x.x

zelenin
Сообщения: 10596
Зарегистрирован: 2013.04.20, 11:30

Re: Вложенные Commands и создание сущностей

Сообщение zelenin » 2017.09.12, 19:09

ну то есть от UI приходит сообщение с галочкой Отправить email. Это конкретный одиночный кейс, а не два разных - обрабатываем в рамках одной команды.

Аватара пользователя
BrusSENS
Сообщения: 496
Зарегистрирован: 2012.07.26, 06:51
Откуда: Новороссийск
Контактная информация:

Re: Вложенные Commands и создание сущностей

Сообщение BrusSENS » 2017.09.12, 19:14

zelenin писал(а):
2017.09.12, 19:09
ну то есть от UI приходит сообщение с галочкой Отправить email. Это конкретный одиночный кейс, а не два разных - обрабатываем в рамках одной команды.
Да. Но по сути у нас есть отдельная команда, которую мы вызываем для повторной отправки, получается у нас появляется один код в нескольких командах? Ведь токен в сообщении является отдельной сущностью.
Native Web - небольшой блог о веб разработке (временно на ремонте)
Режим обслуживания сайта для Yii 2.x.x

zelenin
Сообщения: 10596
Зарегистрирован: 2013.04.20, 11:30

Re: Вложенные Commands и создание сущностей

Сообщение zelenin » 2017.09.12, 19:20

BrusSENS писал(а):
2017.09.12, 19:14
zelenin писал(а):
2017.09.12, 19:09
ну то есть от UI приходит сообщение с галочкой Отправить email. Это конкретный одиночный кейс, а не два разных - обрабатываем в рамках одной команды.
Да. Но по сути у нас есть отдельная команда, которую мы вызываем для повторной отправки, получается у нас появляется один код в нескольких командах? Ведь токен в сообщении является отдельной сущностью.
ничего страшного. если вы хотите выделить повторяющийся код, вы можете это сделать в UserMailSenderService например

Аватара пользователя
BrusSENS
Сообщения: 496
Зарегистрирован: 2012.07.26, 06:51
Откуда: Новороссийск
Контактная информация:

Re: Вложенные Commands и создание сущностей

Сообщение BrusSENS » 2017.09.12, 19:32

zelenin писал(а):
2017.09.12, 19:20
ничего страшного. если вы хотите выделить повторяющийся код, вы можете это сделать в UserMailSenderService например
Понял. Спасибо. Скажите, вот как альтернативу сервису, можно вынести логику отправки сообщения в отдельную команду, а эту команду уже выполнять в обработчике события? Т.е.:

Код: Выделить всё

class UserCreateHandler
{
     public function handle($command)
     {
          ...
          if($command->needSendConfirmation()) {
              $this->eventDispatcher->fire(new ConfirmationSendEvent($user));
          }
     }
}
class ConfirmationSendEventHandler
{
    public function handle()
    {
        $this-commandBus->execute(new SendConfirmationCommand($this->from));
    }
}
А уже в SendConfirmationHandler создаем сущность Token, сохраняем её и отправляем письмо. Как такой вариант?
Native Web - небольшой блог о веб разработке (временно на ремонте)
Режим обслуживания сайта для Yii 2.x.x

Ответить