Анализ Use Cases взаимодействия с модулем аутетнификации. Выделение команд Command и запросов Query. Принцип CQS разделения операций.
Скрытый контент
Чтобы не пропускать новые эпизоды подпишитесь на наш канал @deworkerpro в Telegram
Комментарии (34)
Arunas
Спасибо. Очень интересно.
Bondarenko Alexandr
Спасибо, Дмитрий! Хотел бы посмотреть в вашем исполнении на реализацию подобной ситуации: пользователь регистрируется в контексте аутентификации с ролью покупателя, в контексте покупателей также создается сущность с данными покупателя. Эти данные относятся только к предметной подобласти покупателей ( и не относятся к контексту аутентификации ). Ситуация гиппотетическая.
Ну и вообще очень интересно было бы посмотреть на различные вариации интеграции ограниченных контекстов.
fedot
Это вполне реальная ситуация, у покупателя куча полей, которые не нужны пользователю, например в битрикс так сделано, думаю Дмитрий поступит аналогично, когда дойдет до аукциона.
Ruslan
Спасибо.
Хотелось бы чуть сторонней информации о процессе "проверка приложения" на Facebook.
И еще предположим, что нам ТРЕБУЕТСЯ майл пользователя, а Facebook API не всегда его предоставляет, выдает токин и всё, что будем делать в таком случае?
kafkiansky
Запрашивать почту дополнительно.
Ruslan
Это был намек на добавления этого процесса.
Александр Кулик
Заметил, что изменение скорости воспроизведения видео не работает. Смотрю с мобильного браузера chrome android
Алексей
Непонятно почему DTO назвали Command, ведь Command должен что-то изменять, а DTO объект просто содержит данные.
Дмитрий Елисеев
Есть команда на изменение и её обработчик. Мы отправляем команду с описанием того, что нам нужно сделать. И система её выполняет. Такое именование используется в подходе с Command Bus, где команды даже могут сериализоваться и отправляться в очередь для выпонления в фоне.
Это не классический паттерн Command, где сам объект команды содержит методы execute() и undo(). У нас такой подход будет неудобен, так как обработчику понадобятся зависимые объекты и его конструктор будет ими занят.
Arunas
будет ли очередь (Rabbit или Queue) ?
Дмитрий Ориховский
В описании курса написано "С WebSocket-интерактивом и очередями на RabbitMQ"!
Arunas
да да, спс., незаметил ..:)
Алексей
Если именование Handle ещё не очень критично, так как мы его фактически нигде не вызываем, обычно это command bus, то вот с Command все сложнее. По всему коду и тестам у нас будут new Command(), чтобы понять что это за команда, надо либо полезть в use, либо делать в use алиас, либо перейти в класс команды. Всё это неудобно. Не лучше ли избавиться от папки c названием команды и перенести это в название класса, например, JoinByEmailRequestCommand, JoinByEmailRequestHandler. С одной стороны длинные названия классов, но с другой стороны в сто раз код читабельнее становится
Дмитрий Елисеев
Можно как угодно. Становится читабельнее, но не писабельнее. А так да, если вдруг потребуется использовать две команды в одном классе (что вряд ли произойдёт), то можно использовать алиасы.
Александр
Очень полезный урок, спасибо.
Хотелось бы узнать а как транзакции реализуются при таком подходе, предположим зарегистрировали пользователя (пользователь нужен сразу в контексте авторизации, автора и покупателя), это посути записи в три отдельных контекста.
Пользователь валидный только если он присутствует во всех трёх контекстах одновременно (по разным причинам).
Нам по сути по одному действию (регистрации) нужно кинуть три команды сразу. Две у нас выполняется, третья крашится. По условию пользователь не валидный, что делать в таком случае? В транзакцию это дело не завернешь, ну и две предыдущие нужно откатить как то ?
Дмитрий Елисеев
Это делается через саги, подписываемые на доменные события.
Максим
Ну погодите, как это это через саги?
Мы меняем агрегат (по которому границы транзакционности) в хэндлере, нужно это делать в транзакции в этом же хэндлере!
Единственное, там могут появиться посредники, которые это на себя возьмут, но все же
или я не прав?
Дмитрий Елисеев
Саги как раз и придуманы для осуществления таких транзакций нескольких агрегатов из нескольких контекстов с откатом изменений при ошибке.
Сага напрямую или по событию регистрации отправляет эти команды во все модули. И в случае сообщения об ошибке от любого модуля компенсирующими командами откатывает все предыдущие.
Igor
Спасибо за уроки. Появилось пару вопросов.
"Модуль" Auth это конекст из ддд терминологии?
Содержимое папки Commands это Application Services или Domain Services?
Дмитрий Елисеев
Да, разбиваем на модули по контекстам. А горизонтальное разбиение такое:
Здравствуйте. Спасибо за толковый подход!) А почему структура текущего проекта не такая? Она избыточна?
И ещё один вопрос интересный. Есть мероприятия, на которые регистрируются участники. Сейчас это модуль мероприятий. Но регистрации бывают разных типов и у каждого типа своя регистрация. Я разделил все так:
Event/Registrations/CompetitionEvent/Registrations/Battle
... другие регистрации (приобретение билетов....)
Правильно ли я сделал? Либо регистрацию вынести в отдельный модуль. А в самом модуле только использовать ID мероприятия.
И тогда создание мероприятий будет в разных сущностях и хранится в разных таблицах. В яндекс афише тоже есть подобное. И, как мне кажется, там всё разделено. Даже приобретение билетов...
Дмитрий Елисеев
Если делать полное разбиение по слоям, то нужно ещё и отделить слой инфраструктуры:
Application
Command
Query
Domain
Entity
class Id
interface UserRepository
Service
interface PasswordHasher
interface Flusher
Infrastructure
Entity
class IdType
class DoctrineUserRepository implements UserRepository
Service
class CryptPasswordHasher implements PasswordHasher
class DoctrineFlusher implements Flusher
в который вынести всю работу с Doctrine и прочие "грязные" реализации, а в Domain оставить только чистые сущности и чистые доменные интерфейсы.
Так что да, такое горизонтальное разбиение на папки часто избыточно.
Максим
Видел такое разбиение у вас вhttps://github.com/ElisDN/rabbit-demo-spa. Сейчас проект и подход похожий, поэтому спросил) Понял. Спасибо за ответ!)
Huang
Hello, can you provide a complete framework (folder) template containing three buses (command, query, event), thank you!
Дмитрий Елисеев
Full structure will be complete in this auction project.
Дмитрий Елисеев
Если регистрация производится одинаково, то вынести в Registration c EventId.
Если на Competition и Battle по-разному регистрируются, то проще каждому сделать свою отдельную регистрацию Event/Competition/Registration и Event/Battle/Registration.
Максим
Да, регистрации разные. Кроме того, есть система подсчета результатов для каждого события.
В итоге получается: Event/Competition/Registration Event/Competition/Result
Event/Battle/Registration Event/Battle/Result
Вот и подумал, может это вынести в отдельные системы, как вы говорили про склад и другие...
Артём
Дмитрий, скажите, пожалуйста, если проект у нас полностью на yii2 (advanced) : frontend, backend, api - тогда CQS оставляем в папке апи, а сущности выносим в какую-нибудь папку, например, auction?
То есть структура будет такой:
backend
frontend
api > auth > command
name_project > auth > Entity
Верно размышляю?
Дмитрий Елисеев
Да, в этом случае делаем отдельную папку auction, а в остальных папках осталяем контролеры и представления.
Максим
Кстати. Дмитрий. Нашел очень хороший сервис Auth0 бесплатный тариф до 7000 АКТИВНЫХ пользователей. Отлично подходит на микросервисы. Почему не использовать бы его в своих проектах и не писать каждый раз свой Auth. Понимаю, что мы учимся и Auth может быть со своими требованиями, но не плохой сервис как по мне. Что скажете?)
Дмитрий Елисеев
Да, сервис популярный. Но управление ролями и связывание соцсетей есть только в платном тарифе, который доходит до $1k в месяц. Если нужна двухфакторная аутентификация по SMS, то с хранением телефона на их сервере вероятно нарушается требование о хранении персональных данных в РФ. Амазоновский IP-адрес может случайно попасть под блокировку РКН. Ну и для активной работы всё равно может потребоваться кастомная интеграция. Так что нюансов тоже много.
Максим
Интересно. Приоткрыли глаза)
Жаль. Сервис хороший. Не так сильно его изучал, но теперь буду осторожнее с ним. Думал, что это может быть хорошим решением. Хотелось услышать ваше мнение. Как всегда много полезного! Спасибо ещё раз!
Максим
Здравствуйте) Последняя тенденция модуль Auth называть Id (Identity). Например, Сбербанк ID, DeworkerPro ID. Может быть в этом проекте тоже назвать данный модуль так? Или я не так понимаю? Заодно будет ролик про то, когда меняется что Глобальное)
Дмитрий Елисеев
Да, можно пойти по примеру OpenID и назвать Identity и вынести на поддомен id.xxx. Это дело вкуса.
Спасибо. Очень интересно.
Спасибо, Дмитрий! Хотел бы посмотреть в вашем исполнении на реализацию подобной ситуации: пользователь регистрируется в контексте аутентификации с ролью покупателя, в контексте покупателей также создается сущность с данными покупателя. Эти данные относятся только к предметной подобласти покупателей ( и не относятся к контексту аутентификации ). Ситуация гиппотетическая. Ну и вообще очень интересно было бы посмотреть на различные вариации интеграции ограниченных контекстов.
Это вполне реальная ситуация, у покупателя куча полей, которые не нужны пользователю, например в битрикс так сделано, думаю Дмитрий поступит аналогично, когда дойдет до аукциона.
Спасибо. Хотелось бы чуть сторонней информации о процессе "проверка приложения" на Facebook. И еще предположим, что нам ТРЕБУЕТСЯ майл пользователя, а Facebook API не всегда его предоставляет, выдает токин и всё, что будем делать в таком случае?
Запрашивать почту дополнительно.
Это был намек на добавления этого процесса.
Заметил, что изменение скорости воспроизведения видео не работает. Смотрю с мобильного браузера chrome android
Непонятно почему DTO назвали Command, ведь Command должен что-то изменять, а DTO объект просто содержит данные.
Есть команда на изменение и её обработчик. Мы отправляем команду с описанием того, что нам нужно сделать. И система её выполняет. Такое именование используется в подходе с Command Bus, где команды даже могут сериализоваться и отправляться в очередь для выпонления в фоне.
Это не классический паттерн Command, где сам объект команды содержит методы execute() и undo(). У нас такой подход будет неудобен, так как обработчику понадобятся зависимые объекты и его конструктор будет ими занят.
будет ли очередь (Rabbit или Queue) ?
В описании курса написано "С WebSocket-интерактивом и очередями на RabbitMQ"!
да да, спс., незаметил ..:)
Если именование Handle ещё не очень критично, так как мы его фактически нигде не вызываем, обычно это command bus, то вот с Command все сложнее. По всему коду и тестам у нас будут new Command(), чтобы понять что это за команда, надо либо полезть в use, либо делать в use алиас, либо перейти в класс команды. Всё это неудобно. Не лучше ли избавиться от папки c названием команды и перенести это в название класса, например, JoinByEmailRequestCommand, JoinByEmailRequestHandler. С одной стороны длинные названия классов, но с другой стороны в сто раз код читабельнее становится
Можно как угодно. Становится читабельнее, но не писабельнее. А так да, если вдруг потребуется использовать две команды в одном классе (что вряд ли произойдёт), то можно использовать алиасы.
Очень полезный урок, спасибо.
Хотелось бы узнать а как транзакции реализуются при таком подходе, предположим зарегистрировали пользователя (пользователь нужен сразу в контексте авторизации, автора и покупателя), это посути записи в три отдельных контекста.
Пользователь валидный только если он присутствует во всех трёх контекстах одновременно (по разным причинам).
Нам по сути по одному действию (регистрации) нужно кинуть три команды сразу. Две у нас выполняется, третья крашится. По условию пользователь не валидный, что делать в таком случае? В транзакцию это дело не завернешь, ну и две предыдущие нужно откатить как то ?
Это делается через саги, подписываемые на доменные события.
Ну погодите, как это это через саги?
Мы меняем агрегат (по которому границы транзакционности) в хэндлере, нужно это делать в транзакции в этом же хэндлере!
Единственное, там могут появиться посредники, которые это на себя возьмут, но все же или я не прав?
Саги как раз и придуманы для осуществления таких транзакций нескольких агрегатов из нескольких контекстов с откатом изменений при ошибке.
Сага напрямую или по событию регистрации отправляет эти команды во все модули. И в случае сообщения об ошибке от любого модуля компенсирующими командами откатывает все предыдущие.
Спасибо за уроки. Появилось пару вопросов. "Модуль" Auth это конекст из ддд терминологии? Содержимое папки Commands это Application Services или Domain Services?
Да, разбиваем на модули по контекстам. А горизонтальное разбиение такое:
Здравствуйте. Спасибо за толковый подход!) А почему структура текущего проекта не такая? Она избыточна?
И ещё один вопрос интересный. Есть мероприятия, на которые регистрируются участники. Сейчас это модуль мероприятий. Но регистрации бывают разных типов и у каждого типа своя регистрация. Я разделил все так:
Event/Registrations/Competition
Event/Registrations/Battle
... другие регистрации (приобретение билетов....)Правильно ли я сделал? Либо регистрацию вынести в отдельный модуль. А в самом модуле только использовать ID мероприятия.
Ещё, как вариант, разделить Event по типам:
Event/Competition/Registration
Event/Battle/Registration
И тогда создание мероприятий будет в разных сущностях и хранится в разных таблицах. В яндекс афише тоже есть подобное. И, как мне кажется, там всё разделено. Даже приобретение билетов...
Если делать полное разбиение по слоям, то нужно ещё и отделить слой инфраструктуры:
в который вынести всю работу с Doctrine и прочие "грязные" реализации, а в Domain оставить только чистые сущности и чистые доменные интерфейсы.
Так что да, такое горизонтальное разбиение на папки часто избыточно.
Видел такое разбиение у вас вhttps://github.com/ElisDN/rabbit-demo-spa. Сейчас проект и подход похожий, поэтому спросил) Понял. Спасибо за ответ!)
Hello, can you provide a complete framework (folder) template containing three buses (command, query, event), thank you!
Full structure will be complete in this auction project.
Если регистрация производится одинаково, то вынести в Registration c EventId.
Если на Competition и Battle по-разному регистрируются, то проще каждому сделать свою отдельную регистрацию Event/Competition/Registration и Event/Battle/Registration.
Да, регистрации разные. Кроме того, есть система подсчета результатов для каждого события.
В итоге получается:
Event/Competition/Registration
Event/Competition/Result
Event/Battle/Registration
Event/Battle/Result
Вот и подумал, может это вынести в отдельные системы, как вы говорили про склад и другие...
Дмитрий, скажите, пожалуйста, если проект у нас полностью на yii2 (advanced) : frontend, backend, api - тогда CQS оставляем в папке апи, а сущности выносим в какую-нибудь папку, например, auction? То есть структура будет такой:
Верно размышляю?
Да, в этом случае делаем отдельную папку
auction
, а в остальных папках осталяем контролеры и представления.Кстати. Дмитрий. Нашел очень хороший сервис Auth0 бесплатный тариф до 7000 АКТИВНЫХ пользователей. Отлично подходит на микросервисы. Почему не использовать бы его в своих проектах и не писать каждый раз свой Auth. Понимаю, что мы учимся и Auth может быть со своими требованиями, но не плохой сервис как по мне. Что скажете?)
Да, сервис популярный. Но управление ролями и связывание соцсетей есть только в платном тарифе, который доходит до $1k в месяц. Если нужна двухфакторная аутентификация по SMS, то с хранением телефона на их сервере вероятно нарушается требование о хранении персональных данных в РФ. Амазоновский IP-адрес может случайно попасть под блокировку РКН. Ну и для активной работы всё равно может потребоваться кастомная интеграция. Так что нюансов тоже много.
Интересно. Приоткрыли глаза) Жаль. Сервис хороший. Не так сильно его изучал, но теперь буду осторожнее с ним. Думал, что это может быть хорошим решением. Хотелось услышать ваше мнение. Как всегда много полезного! Спасибо ещё раз!
Здравствуйте) Последняя тенденция модуль Auth называть Id (Identity). Например, Сбербанк ID, DeworkerPro ID. Может быть в этом проекте тоже назвать данный модуль так? Или я не так понимаю? Заодно будет ролик про то, когда меняется что Глобальное)
Да, можно пойти по примеру OpenID и назвать Identity и вынести на поддомен id.xxx. Это дело вкуса.
Или войти через: