Задать вопрос
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'а и деплою их отдельно. Есть ли смысл разделять версии в рамках единой кодовой базы проекта и как (неймспейсы, конфиг, модуль, что-то ещё)?

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

Заранее спасибо тем, кто осилит длиннопост, поделится своим опытом и, возможно, пришлёт пример на код подобного проекта.
  • Вопрос задан
  • 6046 просмотров
Подписаться 41 Сложный Комментировать
Решение пользователя index0h К ответам на вопрос (5)
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 раз больше, чем кода.
  • Жесткие, обязательные кодревью.
  • Если задача крупная - декомпозируйте ее.
  • Технический долг - возвращайте обязательно И как можно скорее.
  • Перед тем как писать код для работы с внешним сервисом - имеет смысл написать его эмулятор.
  • Спешите только в случае серьезных проблем на проде)). Фичи "на вчера" отличаются от фич "на потом" только приоритетом выполнения, более ничем.
Ответ написан