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