Страница 1 из 1
Вложенные Commands и создание сущностей
Добавлено: 2017.09.12, 03:09
BrusSENS
Привет всем. Появился вопрос.
Есть у нас команда 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))? Или всё таки писать для конвертации состояний отдельный маппер?
Re: Вложенные Commands и создание сущностей
Добавлено: 2017.09.12, 09:01
ElisDN
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. Фабрика создаёт объекты. Если это как-то связано с созданием, то в неё.
Re: Вложенные Commands и создание сущностей
Добавлено: 2017.09.12, 14:46
BrusSENS
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.
Re: Вложенные Commands и создание сущностей
Добавлено: 2017.09.12, 15:35
zelenin
BrusSENS писал(а): ↑2017.09.12, 14:46
А как же без гидрации тогда извлекать свойства сущности? У User есть свойство passwordHash, но по логике его нельзя получить через геттер. Получается, что только гидрация.
почему мы его не можем получить через геттер?
Re: Вложенные Commands и создание сущностей
Добавлено: 2017.09.12, 17:41
BrusSENS
zelenin писал(а): ↑2017.09.12, 15:35
почему мы его не можем получить через геттер?
Так а зачем давать возможность получить хэш пароля? Для смены пароля и проверки соответствия есть методы changePassword() и isPasswordRelevant(). Разве мы должны давать прямой доступ к тем или иным хэшам?
Re: Вложенные Commands и создание сущностей
Добавлено: 2017.09.12, 18:02
zelenin
BrusSENS писал(а): ↑2017.09.12, 17:41
zelenin писал(а): ↑2017.09.12, 15:35
почему мы его не можем получить через геттер?
Так а зачем давать возможность получить хэш пароля? Для смены пароля и проверки соответствия есть методы changePassword() и isPasswordRelevant(). Разве мы должны давать прямой доступ к тем или иным хэшам?
так, логично.
создание и изменение сущности в рамках бизнес-процессов должно осуществляться с помощью публичного апи домена (конструктор, фабрика, методы сущности). Во всех остальных случаях (восстановление из базы/сохранение в базу - не бизнес, а технические) без помощи апи - например гидратор, основанный на рефлексии.
В данном случае фабрика - это класс, знающий как создавать сущность в контексте публичного апи.
И вот учитывая все это наш password_hash сохранит и восстановит гидратор.
Re: Вложенные Commands и создание сущностей
Добавлено: 2017.09.12, 18:47
BrusSENS
zelenin писал(а): ↑2017.09.12, 18:02
И вот учитывая все это наш password_hash сохранит и восстановит гидратор.
Вот об этом я и говорю.
Дмитрий написал, что
ElisDN писал(а): ↑2017.09.12, 09:01
Гидрацию используйте только при извлечении из БД в репозитории
Потому и говорю, что гидрация может понадобиться не только при восстановлении объекта.
Re: Вложенные Commands и создание сущностей
Добавлено: 2017.09.12, 18:51
BrusSENS
Zelenin, вот интересует в рамках данной темы то, как же всё таки реализовывать, например отправку E-mail сообщений, если, например, в админке у меня есть возможность указать флаг, отправлять E-mail новому пользователю, или нет? Разные команды? Последовательное исполнение нескольких команд в зависимости от флага? Какой вариант решения будет лучше?
UPD: ещё вопрос: насколько верно назвать методы обращения и восстановления объекта как serialize() и unserialize() согласно UL?
Re: Вложенные Commands и создание сущностей
Добавлено: 2017.09.12, 18:55
zelenin
BrusSENS писал(а): ↑2017.09.12, 18:51
Zelenin, вот интересует в рамках данной темы то, как же всё таки реализовывать, например отправку E-mail сообщений, если, например, в админке у меня есть возможность указать флаг, отправлять E-mail новому пользователю, или нет? Разные команды? Последовательное исполнение нескольких команд в зависимости от флага? Какой вариант решения будет лучше?
конкретный кейс какой? что происходит когда отправляем письмо?
BrusSENS писал(а): ↑2017.09.12, 18:51UPD: ещё вопрос: насколько верно назвать методы обращения и восстановления объекта как serialize() и unserialize() согласно UL?
это технические моменты, не относящиеся к доменному знанию и UL. Что такое сериализация можно посмотреть в вики.
Re: Вложенные Commands и создание сущностей
Добавлено: 2017.09.12, 19:06
BrusSENS
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()
);
}
}
Re: Вложенные Commands и создание сущностей
Добавлено: 2017.09.12, 19:09
zelenin
ну то есть от UI приходит сообщение с галочкой Отправить email. Это конкретный одиночный кейс, а не два разных - обрабатываем в рамках одной команды.
Re: Вложенные Commands и создание сущностей
Добавлено: 2017.09.12, 19:14
BrusSENS
zelenin писал(а): ↑2017.09.12, 19:09
ну то есть от UI приходит сообщение с галочкой Отправить email. Это конкретный одиночный кейс, а не два разных - обрабатываем в рамках одной команды.
Да. Но по сути у нас есть отдельная команда, которую мы вызываем для повторной отправки, получается у нас появляется один код в нескольких командах? Ведь токен в сообщении является отдельной сущностью.
Re: Вложенные Commands и создание сущностей
Добавлено: 2017.09.12, 19:20
zelenin
BrusSENS писал(а): ↑2017.09.12, 19:14
zelenin писал(а): ↑2017.09.12, 19:09
ну то есть от UI приходит сообщение с галочкой Отправить email. Это конкретный одиночный кейс, а не два разных - обрабатываем в рамках одной команды.
Да. Но по сути у нас есть отдельная команда, которую мы вызываем для повторной отправки, получается у нас появляется один код в нескольких командах? Ведь токен в сообщении является отдельной сущностью.
ничего страшного. если вы хотите выделить повторяющийся код, вы можете это сделать в UserMailSenderService например
Re: Вложенные Commands и создание сущностей
Добавлено: 2017.09.12, 19:32
BrusSENS
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, сохраняем её и отправляем письмо. Как такой вариант?