TrogWarZ
@TrogWarZ
web developer

Проект со сложной логикой на Symfony – как проектировать? Примеры?

Здравия!

NOTE: сразу прошу прощения за длиннопост.

Начинаю проектировать новый проект, в котором будет:
* Много странной/сложной логики (вроде "когда пятая планета в фазе сириуса на шестом дне от седьмой среды после 42 недели високосного года, но только если во-о-о-он тот пост не старше недели"), в которой критериев для состояния чего-либо может быть не один уровень. Однако, Внешнее API немногим сложнее типичного CRUD'а.
* Уровни доступа для групп пользователей (которые можно создавать через админку) вплоть до каждого параметра некоторых бизнес-моделей. Например:"Вот в этой табличке вон тот столбец могут видеть только такие-то группы юзеров".
* Много дополнительных автоматических действий при совершении какого-либо действия типа "при изменении значения вот в этой ячейке этой таблицы сделать то, сё, пятое, десятое и компот". Цепочки могут быть достаточно длинными и глубокими чтобы не помнить их не вооружившись ТЗ. Многие из них должны быть отложенными или асинхронными ибо могут быть тяжёлыми.
* Основная работа – с внешними сервисами по API, с дублированием некоторых сервисов на уровне бизнес логики типа "Если при совершении действия сервис1 отвалился – пробовать сервисы 2,3,...,100500 пока не получится, в таком-то порядке по такому-то критерию".
* Долгоживучесть проекта – и поддерка, и допиливание логики, которую невозможно предусмотреть сейчас. Годами.
* Проект будет представлять из себя API – никаких шаблонов/вёрстки/фронта там не предполагается. Точнее, оно будет, но отдельным проектом, возможно, на Angular.

В качестве фреймворка предполагаю взять Symfony – с ним ещё не довелось работать, но, судя по их книге, кажется, не нужно будет бороться с ним в проекте такого типа.

Ближе к вопросам.

Чтобы всё не превратилось в очень жирные модели – репозиторий отдельно, модели отдельно (Доктрина, я читал, умеет – велосипед не надо изобретать). Как хранить бизнес-логику чтобы модели не превратились в монстров из десятков тысяч строк? Читал про Command Bus где, если правильно понял, на каждое действие в системе – свой класс? Как их организуете (их тогда будут сотни)?
Есть ли смысл выносить каждую доменную модель в модуль/микросервис, хранить всю связанную логику где-то там внутри, а с остальными общаться по внешнему API? За ответы в клиентскую часть – отдельный сервис-фронтенд? Каков оверхед? Используют ли такое на практике? Какие подводные камни?

Раньше для дополнительных действий мне достаточно было использовать что-то вроде beforeUpdate/afterCreate модели. Сейчас же эти методы рискуют стать гигантскими и очень сложными даже если выносить логические куски в отдельные методы. К тому же, многие из них будут вынесены в очередь задач (в rabbitmq, например) и обработаны асинхронно. Но некоторые – нет и в каких-то случаях будет очень важна последовательность выполнения хуков. Как не превратить кидание/получение событий типа PostBeforeEdit/PostBeforeEditHandler в "callback hell"? В одном похожем проекте я такое видел (правда, на js) – при каждом клике никогда не знаешь сколько десятков действий произойдёт, даже вооружившись отладчиком. Было бы хорошо держать конфиг подобной логики человекочитабельным и в одном месте – как вы это решаете?

ACL. Где храните указанную логику? Сетку предполагаю хранить в базе и кэшировать. Какие структуры для описанного выше – best practice? В моём понимании это выглядит как куча трёхмерных кубов доступа "crud – group – entity – field", как это сделать более плоским пока только одна идея – делать кучу таблиц many-to-many. Но мне кажется, что работа с этими "джойнами" при каждом запросе будет вторым узким местом после внешних api.

Версионирование. Как вы версионируете подобные проекты? А если нужна "N-1" рабочая версия на продакшене? Сейчас храню в разных ветках git'а и деплою их отдельно. Есть ли смысл разделять версии в рамках единой кодовой базы проекта и как (неймспейсы, конфиг, модуль, что-то ещё)?

И, самое главное – как всё это совместить? По отдельности вышеописанное уже применял на практике. Но всё и сразу в голове не укладывается. На какие проекты (точнее, на код) можете посоветовать посмотреть для лучшего понимания? Ссылки на репозитории?

Заранее спасибо тем, кто осилит длиннопост, поделится своим опытом и, возможно, пришлёт пример на код подобного проекта.
  • Вопрос задан
  • 6010 просмотров
Решения вопроса 2
index0h
@index0h
PHP, Golang. https://github.com/index0h
Как хранить бизнес-логику чтобы модели не превратились в монстров из десятков тысяч строк?

Тут не совсем модели. Entity - это просто объект данных, умеет хранить их в себе и бросать исключения, если не правильные данные вставляете, все. Repository - умеет работать со своим Entity И БД.

БЛ находится в классах сервисах.

Читал про Command Bus где, если правильно понял, на каждое действие в системе – свой класс?

Вообще под каждое дествие - не обязательно, тут нужно руководствоваться здравым смыслом.

Как их организуете (их тогда будут сотни)?

Иерархически. Путь к классу должен быть "понимаем".

Есть ли смысл выносить каждую доменную модель в модуль/микросервис, хранить всю связанную логику где-то там внутри, а с остальными общаться по внешнему API?

Только в тому случае, если вы уже делали такие же проекты и в точности знаете максимально точно границы каждого домена. Иначе - не стоит. Делайте монолитную систему, а разделение на микросервисы - только по факту необходимости.

За ответы в клиентскую часть – отдельный сервис-фронтенд?

Если в "сервис" вы вкладываете понятие простого класса, умеющего форматировать ответы вашего проекта - мысль здравая.
Если ответы будут асинхронными (от сервера к другому) - имеет смысл выностить в отдельный клиентский класс.

Каков оверхед?

Ничтожный.

Используют ли такое на практике?

Да

Какие подводные камни?

Следствием серьезной декомпозиции в любом случае будут лишние сущности, чем раньше от них будете избавляться - тем лучше.

Как не превратить кидание/получение событий типа PostBeforeEdit/PostBeforeEditHandler в "callback hell"?

Если есть возможность отказаться от событийной модели - часто лучше отказаться.
Листнеры доктрины конечно штука мощная, но работает не всегда очевидно.

Функционал "PostBeforeEdit/PostBeforeEditHandler" часто дешевле и проще вынести в сервис, но опять же руководствуйтесь здравым смыслом.

ACL Где храните указанную логику?

Если ACL будет не тривиальный - готовьте себя к тому, что он будет размазан по уровню контроллеров.

Какие структуры для описанного выше – best practice?

Если есть возможность привести к одноуровневому виду - сделайте. Если с точки зрения бизнеса может потребоваться иерархическая (не фиксированной вложенности) ACL - до последнего убеждайте, что это плохая идея, не повторяйте чужих ошибок.

В моём понимании это выглядит как куча трёхмерных кубов доступа "crud – group – entity – field", как это сделать более плоским пока только одна идея – делать кучу таблиц many-to-many.

Гибкая настройка вплоть до каждого поля 90% что не нужна. Если можно свести к понятию скопов прав - сделайте это.
Структуру можно предлагать только зная ваш проект.

Версионирование. Как вы версионируете подобные проекты?

Semver.

А если нужна "N-1" рабочая версия на продакшене?

Значит на прод попадает ваша версия с тегом "N-1"))

Есть ли смысл разделять версии в рамках единой кодовой базы проекта и как (неймспейсы, конфиг, модуль, что-то ещё)?

Храните яйца в отдельных корзинках. Если модуль развивается полностью отдельно и может быть вынесен как зависимость проекта в vendor - делайте.

И, самое главное – как всё это совместить?

  • РУКОВОДСТВУЙТЕСЬ ЗДРАВЫМ СМЫСЛОМ
  • Принимаете жесткие соглашения по правилам написания кода, например такие
  • Постарайтесь убедить бизнес в том, что без покрытия кода автотестами будет дороже, нестабильней и дольше. + Пишите тесты. Если объем тестов в 4 раза больше кода, который они тестируют - это норм. У меня бывали случаи, когда для критичного функционала тестов было в ~16 раз больше, чем кода.
  • Жесткие, обязательные кодревью.
  • Если задача крупная - декомпозируйте ее.
  • Технический долг - возвращайте обязательно И как можно скорее.
  • Перед тем как писать код для работы с внешним сервисом - имеет смысл написать его эмулятор.
  • Спешите только в случае серьезных проблем на проде)). Фичи "на вчера" отличаются от фич "на потом" только приоритетом выполнения, более ничем.
Ответ написан
Fesor
@Fesor
Full-stack developer (Symfony, Angular)
Как их организуете (их тогда будут сотни)?


Раскидываю по неймспейсам. Скажем все действия относящиеся к юзерам находятся в папке Users.

Только вы учитывайте что CQRS это прикольно но особо не нужно. К примеру это сразу подразумевает что вы используете UUID вместо автоинкрементов и прочей чуши. Можете сделать хотя бы как Дядя Боб предлагает в своей Clean Architecture. Просто сервис на каждое действие.

Есть ли смысл выносить каждую доменную модель в модуль/микросервис


Если вы не умеете писать монолиты так что бы потом все было относительно нормально, думать о микросервисах не стоит. Так же как делить проект на модули когда вы еще слабо представляете как прописывать границы этих модулей.

Раньше для дополнительных действий мне достаточно было использовать что-то вроде beforeUpdate/afterCreate модели.


ну а сейчас вы будете эти вещи в сервисы пихать. Причем возможно не в один сервис. Вообще старайтесь не делать "хуков" и не будет с ними проблем. Есть к примеру DomainEvents такая штука, ну и можно все эти "дополнительные действия" в хэндлерах команд делать.

Как не превратить кидание/получение событий типа PostBeforeEdit/PostBeforeEditHandler в "callback hell"?


Просто забудьте об этих ивентах.

ACL. Где храните указанную логику?


Есть в симфони security vouters, а дальше все зависит от того что вы делаете.

Как вы версионируете подобные проекты? А если нужна "N-1" рабочая версия на продакшене?


git + docker теги в мастере. Ветки нужно плодить только тогда, когда у вас система деплоится кастомерам и нужно поддерживать сразу кучу версий. Называется это gitflow.

На какие проекты (точнее, на код) можете посоветовать посмотреть для лучшего понимания? Ссылки на репозитории?

На гитхабе катострофически мало примеров хороших приложений на симфони. Да и не только на симфони - в принципе найти в открытом доступе сложный проект - это нереально. NDA и все такое. Такие системы обычно очень дорогие и закрытые со всех сторон.

p.s. почитайте книжки:

- Эрик Эванс - Предметно ориентированное проектирование
- Крэйг Ларман - Применение UML 2.0

p.p.s. Все ваши загоны не имеют никакого смысла если вы не будете пользоваться практиками вроде Test-Driven-Development, ну или хотя бы покрывать систему интеграционными тестами. Без этого вы не сможете делать частный мелкий рефакторинг, а без этого ваша система быстро превратится в легаси.
Ответ написан
Пригласить эксперта
Ответы на вопрос 3
Adamos
@Adamos
> способ сделать плоским наследование групп типа "manager может делать всё то, что и customer, но ещё вон то и вот это"? Резоннее и более поддерживаемо будет дублировать все права?

Имхо, все эти заморочки с наследованием групп нужны только для удобства администрирования. И место им - в админке, а не в группах. Сделайте удобное редактирование групп, со сравнением с другой группой. Или шаблоны, позволяющие применить пользователю сразу несколько групп. Даже более сложные вещи типа того, что группа менеджеров не может быть применена в отрыве от кастомеров.
Но все это - не в группах, а именно в этом разделе админки. Назначаются-то группы далеко не так часто, как используются. А вот для использования им стоит быть максимально простыми.
Ответ написан
@saritskiy
Чтобы не размазывать ACL и не увеличивать размеры контроллеров до безумного размера взгляните на аспекты. https://github.com/goaop/goaop-symfony-bundle. По поводу того что постоянно придется дергать какие-то данные и постоянно их пересчитывать и прочее, посоветую взглянуть в сторону тарантула. Высоко нагруженные сервисы типа Badoo и Mail.ru хранят в нем пользовательские сессии и прочую горячую информацию.
Ответ написан
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Похожие вопросы