Регистрация пользователей и тесты

Моделирование юзкейса запроса регистрации по email. Декомпозиция объектов. Абстрактные типы данных. Проработка сущностей, агрегатов и сервисов через Unit-тесты подходом Test First. Написание и тестирование доменных сервисов.

Комментарии (21)
Arunas
2020-01-24 19:03

спасибо

Ответить
Bondarenko Alexandr
2020-01-25 08:21

Дмитрий, спасибо! Не планируете ли рассмотреть подход с валидацией всех данных сущности отложено, чтобы, к примеру, дать более информативный ответ пользователю о том, какие данные были введены неверно. К примеру М. Фаулер писал о неком Шаблоне Увеломления ( Notification Pattern ) ( https://martinfowler.com/articles/replaceThrowWithNotification.html, https://martinfowler.com/eaaDev/Notification.html, . В текущем подходе, при модификации данных сущности, исключение будет выброшено при обнаружении первого невалидного аргумента. До остальных проверок не дойдет и пользователь не узнает, что он где-то еще мог допустить ошибку при вводе данных.

Ответить
Алексей
2020-01-25 08:46

Первичную валидацию (на обязательность и пр.) данных можно сделать в экшене или command через Assert::lazy(), а дальше не вижу ничего страшного выводить по одной ошибки при уже логических ошибках

Ответить
Bondarenko Alexandr
2020-01-25 08:55

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

Ответить
Алексей
2020-01-25 08:43

Не является ли использование флашера отдаленной привязкой к Doctrine, ведь не все библиотеки для работы с БД работают по такой схеме? И является ли использование флашера вообще правильным, ведь мы отправляем в БД сразу вообще все изменения, о которых наш Handler даже может и не знать. Мне кажется, что сохранять в БД нужно уже в репозитории, причем весь агрегат вместе с дочерними сущностями (например user, user_profile) и только их и внутри транзакции, а не всё, что накопил EM. Что думаете?

Ответить
Bondarenko Alexandr
2020-01-25 09:26

Если инфраструктура может предоставить Api именно для таких взаимодействий: $this->users->add($user), $this->flusher->flush(), то тут никуда не деться. В прикладном слое некоторые детали взаимодействия с инфраструктурой знать нормально, но опосредовано. Что касается гибкости, мне кажется, что тут тоже особо нет проблем. Если перейти на стиль взаимодействия с бд, когда при вызове метода $this->users->add($user) данные будут сразу вставляться в бд, можно, к примеру, инжектить заглушку Flusher'a ( но лучше, конечно, хранить код в чистоте и вообще не инжектить, если перешли на другую ORM ).

Ответить
Bondarenko Alexandr
2020-01-25 09:28

Да и сложно представить ситуацию, когда используешь ORM с UoW и переходишь на более простой вариант ORM.

Ответить
Алексей
2020-01-25 08:49

Дмитрий, Вы выбрали использовать простые типы данных в командах, преобразуя их в DTO уже в Handler. Я видел еще 2 варианта, кто-то сразу в команду передает DTO, кто-то передает в команду простые типы и в конструкторе делает DTO. Никак не могу понять, есть ли какая-то разница кроме вкусовщины?

Ответить
Bondarenko Alexandr
2020-01-25 09:07

Вы имеете ввиду VO, не DTO. И тут правильнее сказать, что VO инициализируются в хэндлере, не преобразуются. Вне прикладного слоя инициализировать не норм. Во-первых, внешним слоям программы ( контроллеры, cli-команды ) не следует знать о доменном слое. А VO - это элемент модели, Во-вторых, если HttpController'e вызывает некий Handler, а вы захотите вызывать Handler еще из Cli, вам придется дублировать логику инициализации VO.

Ответить
Алексей
2020-01-25 09:09

Да-да, я про VO, прошу прощения)) В таком случае можно инициализировать в команде, это разгрузит код хэндлера

Ответить
Bondarenko Alexandr
2020-01-25 09:12

Считаю, что в прикладной команде тоже не норм инициализировать VO. Это просто объект для переноса данных в прикладной слой.

Ответить
Konstantin
2020-03-10 07:02

А может быть уже есть библиотека, в которой определены сущности Entity? Email и т.п.

Ответить
Максим
2020-03-10 12:07

Здравствуйте.

В ваших роликах, при проектировании, сущности всегда встречал повторяющиеся Value Objects: ID, Status, Role, Name, Email.

Каждый раз, при проектировании какой-либо сущности, вы копируйте ID, который является UUID. И таким образом мы получаем копипасту, в тестах и сущностях.

Если в наших проектах Status, Role, Name как-то может поменяться, то ID и Email никогда не меняется. Таким образом его можно вынести в отдельную папку Value Object и использовать от туда. Но вы этого не делаете. Почему?) вместо этого у вас ID на каждую сущность. Можете пояснить по этому вопросу?

И ещё один вопрос «является ли status, role» VO?

Ответить
Deworker Pro
2020-03-12 09:10

Да, это всё Value Objects.

Наличие своего Id у каждой сущности избавляет от путаницы при типизации. Если есть метод:

class Project
{
    public function assign(Member\Id $member, Position\Id $position) { ... }
}

то в него никто вместо member->id случайно не передаст position->id или company->id.

Если же будет один общий класс Id или Uuid на всю систему, то может быть путаница.

Ответить
Deworker Pro
2020-03-12 09:27

Можно вынести общий абстрактный класс Id и наследовать все User\Id уже от него. Но будут неудобства, если некоторые идентификаторы вдруг понадобится сделать числовыми инкрементными. Тогда придётся делать ещё один отдельный базовый класс.

В данном случае можно безболезненно вынести Email. Но и уже в этом можем заметить, что агрегат User использует его метод isEqualTo, а другим сущностям из другого модуля этот метод может быть не нужен.

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

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

Так что выносить или нет - вопрос не тривиальный.

Ответить
Максим
2020-03-12 11:21

Теперь понял))) "Шубка выделки не стоит") Благодарю!

Ответить
Дмитрий
2020-03-14 11:26

А что произойдет если мы в какой то момент захотим изменить алгоритм хэширования пароля? Как мы будем проверять пароль? придется проверять все использованные ранее алгоритмы? или просто пользователи будут вынуждены восстатавливать свои пароли?

Ответить
Deworker Pro
2020-03-14 12:16

Если поменяем алгоритм в password_hash, то ничего не сломается, так как password_verify продолжит работать со всеми возможными алгоритмами.

Ответить
Олег
2020-05-17 20:09

Очень похоже на ваш доклад с PhpRussia 2019 )

Ответить
Deworker Pro
2020-05-19 16:02

Да. Доклад как раз был по коду прошлогоднего проекта на Symfony. И сюда многое оттуда перешло.

Ответить
Зарегистрируйтесь или войдите чтобы оставить комментарий