Про фреймворки и сервисный слой
После нашего отчёта о конференции по разрешению координатора Егора публикую запись своего доклада на PHP Russia. Покапитанил про важность разделения кода и инкапсуляции бизнес-логики в сервисах и сущностях:
- Видео на YouTube на официальном канале
- Тезисы и слайды в PDF со ссылкой на Google Docs
- Плейлист лучших докладов по результатам голосования
- Сайт конференции с остальными выступлениями
Доклад построен на основе материалов мастер-класса по Symfony с Doctrine. Также более подробные примеры проектирования есть в эпизодах о конструкторах и методах нашей базы знаний.
Везде меня веселее смотреть на скорости 1,25. И обязательно подписаться на мою рассылку:
"Слайды на 100 страниц. Извините))" - классика))
Дмитрий молодец!
Классное видео, очень просто и полезно!
Здравствуйте, Дмитрий!
Очень увлекательный доклад, спасибо.
Есть один вопрос:
Что делать, если бизнес-логика зависит от юз-кейса?
Приведу синтетический пример:
Но, к примеру, если это пост публикует админ, то это условие не нужно применять.
Бизнес-логика должна лежать в слое домена, но что делать, если в каком-то юз-кейсе бизнес-правила отличаются или даже противоречат общим? Писать бизнес-логику в слое приложения в юз-кейсах ведь не правильно.
Но и написать 2 метода в модели для этих целей не выглядит правильным решением, потому что тогда есть способ обойти инварианты в клиентском коде.
Как Вы считаете? Спасибо
Делаю publishByAuthor() и publishByAdmin().
Не будем смотреть на тесты.
Вы можете вызвать publishByAdmin там, где это не стоит делать и с точки зрения DDD инвариант будет нарушен, потому что метод модели семантически зависит от юз-кейса
С точки зрения сущности всё правильно. Это юзкейс должен решить, админ это или нет.
Дмитрий, спасибо за то, что вы делаете!
Спасибо за доклад, Дмитрий!
А бывает случай, когда в микросервисной архитектуре бизнес-логика размазана по сервисам.
К примеру, удалить сущность можно только тогда, когда другой сервис дает добро.
Например, удалить пользователя (сервиса пользователей), у которого нет лотов (http кол в лот сервис).
Куда в этом случае ложить эти проверки и на каком уровне защищать это бизнес правило, если оно неделимо?
В микросервисах это либо дёргается через HTTP из Gateway, либо работает на событиях и управляется сагами.
Дмитрий, есть ли в планах провести мастер-класс или написать статью по сагам? Интересует saga orchestration.
Да, можно что-нибудь такое придумать.
Очень крутой доклад. Спасибо.
Есть ли существенные минусы у Доктрины?
Есть ли сейчас достойная замена Доктрине? Какая-либо другая библиотека, позволяющая писать код по DDD. Чтобы уровень удобства был не меньше чем у Доктрины.
Из неудобств - требование использовать её ArrayCollection для связей и необходимость передачи объекта для связей.
По её аналогии, но с экономией памяти для асинхронных приложениях пишут Cycle ORM.
А если Doctrine недостаточно (что редкость), то уже пишут свои репозитории и гидраторы.
Спасибо )
1. В чем профит создания связи с помощью Member/Id, почему не передаете непосредственно объект Member в конструктор лота? Ну и соответственно memberId - строка, вместо ManyToOne?
2. Если работаем со ставками через лот, то как быть, если, к примеру, надо вывести постранично список всех ставок? Работать с массивом объектов может быть накладно
1. Без Doctrine (которой для связи нужна целая сущность) в сущность нет смысла создавать и передавать целого мембера, если от него нужен только ID. Особенно в тестах.
2. Выполняем напрямую SELECT FROM bids INNER JOIN lots и выводим список. Тяжёлые доменные сущности в листингах выводить нецелесообразно и по производительности.
1. А с доктриной? Все-таки в примере используется она
2. Т.е. для вывода данных доменные сущности не используются? В таком случае в каком слое будет находится код, реализующий эту функциональность?
1. С Doctrine для связей передаётся весь Member.
2. Верно. Доменные сущности только для домена. А для вывода делают отдельные ReadModel.
Под UseCase (если прямо по тексту то что-то типа UseCase\SignUp\Request) подразумевается namespace для объединения Command и Handler или я где-то пропустил все-таки отдельный класс под UseCase? В коде презентации в контроллерах же просто везде хендлер дергается с передаваемой ему командой!
Именно операция. А одним классом-сервисом или пачкой классов в папке в папке - не важно.
А если вот у меня например use case можно обозвать как GetRate, "Получить цену". То есть я хочу в API, web-морду, CLI или еще как-то вернуть некую цену на мой товар. Наверное, тогда стоит именовать классы GetRateQuery и GetRateQueryHandler соответственно. И у GetRateQueryHandler должен быть, наверное, не handle(): void, а что-то типа getResult():mixed ?
Да, запросы можно как раз именовать как Query.
Да у меня тут больше вопрос был про handle():void, если для Command, и hande():mixed для Query. То есть нужно ли разделять хендлеры (по их интерфейсам имею ввиду) для Command и для Query, ибо, судя по всему, в ответ на запрос все-таки нужно что-то более существенное чем void вернуть :).
Да, можно для запросов сделать папку вроде Query или ReadModel как здесь.
Спасибо!
Дмитрий есть ли в планах рассказать о Specification pattern и в частности применение его в репозиториях для выборки объектов из базы данных?
Можно попробовать. Но обычно проще делать запросы без них.
Получается мы в полностью отказываемся от фреймворковского взаимодействия с базой и его ActiveRecord (или что там есть во фреймворке)?
Если используем Doctrine, то да. Многие идеи описывал в серии по проектированию сущностей.
Курто! Спасибо большое за вашу работу. У вас где посмотреть список полезной литературы на сайте, чтоб не читать все подряд?
Предметно-ориентированное проектирование (DDD) (Эрик Эванс)
Реализация методов предметно-ориентированного проектирования (Вон Вернон)
Советую свежий мастер-класс про Slim и React. Там это всё применяем на практике.