Комментарии 15
Сложно каталогизировать сервисы-куски бизнес-логики, легко пропустить существующий сервис. Нужен кто-то с хорошей экспертизой проекта, кто будет за этим следить.
Ну если совсем честным быть, то в при любом подходе, на сложном проекте нужен кто-то с хорошей экспертизой проекта. Это, как мне кажется, не технический вопрос.
Да, сам факт необходимости в таких людях, конечно не оспаривается.) Я это писал к тому, что когда проектируешь какой-то кусок, постоянно приходится думать: вот придут ребята, начнут его дописывать по-своему, использовать по-своему, а кто-то не увидит и сделает похожее.
Эти вопросы всегда висят в воздухе и постоянно приходится думать, как реализовать удобное для переиспользования и сопровождения решение. Желательно так, чтобы вообще не пришлось объяснять, что это и зачем. И уж тем более, чтобы не следить потом зорким глазом на ревью, ударяя по рукам всякому, кто отклонился от твоего замысла.)
Ну во первых entity классы не всегда пишутся руками, они еще могут и генерироваться из описания к примеру. Там уже логику замучаешься втискивать. Потому имхо, если уж решили логику отдельно от данных держать, то везде, во всех проектах.
Во вторых, если понадобятся dto, то придется писать еще отдельные классы dto, а так можно взять эти пустоголовые классы и использовать в межмодульном общении.
Во третьих, логика еще подразумевает обращение к каким то сторонним сервисам. Вы для этого создали по сути тот же сервис ActivateBonusAccount со странным именем больше подходящим какому то методу. Уж тогда BonusAccountActivationService ;). И получилось, что у вас логика размазалась. Может потом в условии активации надо будет опросить еще и какой то сторонний сервис?
В четвертых, Если у меня есть UserEntity и есть UserService с методом activateUser, почему мне должно прийти в голову писать еще один сервис с таким же методом? Как можно этот сервис не заметить? Нашел UserEntity и не нашел UserService? Либо бардак с именованием в проекте, или программируете в блокноте.
Таки и что вы предлагаете? Тащить бизнес-логику в модели, как в yii, и получать классы на 10к строк и с зависимостями на половину проекта?
CQRS задуман для распиливания толстых сервисов. Но навигации по классам будет больше, увы.
Я в геймдеве писал именно полноценные модели, которые держали весь код БЛ. Ни 1 класса на 10 к строк кода ни разу не было. Даже на 1к были только очень редкие специализированные классы, например, обработчики матриц и векторов, что обязательно надо делать в 1 методе, из-за чего они были весьма длинные и тяжёлые. А БЛ в геймдеве обычно много сложнее чем на любом беке сайтов, что я видел.
При этом не делал впадая в другую крайность - отдельный класс на каждый чих. Впрочем, как и тут в команду логику я не выносил) Грамотно спроектировать участвующие сущности в классы, да и всё.
Про кучу зависимостей - часто видел как раз с контроллерами. Там на входе using или import на десятки строк норма, а в некоторых и больше ста встречал. Разделение же по логике требует высокий уровень понимания разделения ответственности, но даёт минимум сторонних зависимостей
Не со всем согласен, но статья отличная, спасибо. DTO это все же предельный, вырожденный случай анемичной модели, откуда выброшено все, в том числе и валидация состояния.
Тут на самом деле вопрос нехитрый: если ваши модели имеют транспортную функцию, будь-то между системами, для сериализации куда-то, для сохранения в БД, они должны быть анемичнее некуда, максимум - иметь методы для сериализации и преобразования. Если это модели для бизнес-логики, то там бизнес-логика вполне себе может быть уместной, например, связанная с валидацией полей самой модели.
нужно реализовать активацию и деактивацию бонусного счета в зависимости от сложной логики смены статуса клиента.
...
Чтобы включить старую бизнес-логику в новую, вам придется сделать одно из двух: либо продублировать код со всеми вытекающими, либо запутать код, притащив BonusAccountService в зависимости нового сервиса, и вызывая его там.
Это вообще нормально, что при каждом изменении правил со стороны бизнеса нужно менять код приложения и раскатывать клиентам новую версию? Вряд-ли авторы DDD имели ввиду именно это, когда предлагали помещать бизнес-логику в модели домена.
Запутывается... Нужен эксперт...
Начало любой задачи начинается с поиска уже имеющейся реализации. Даже поиск слова “bonus” по проекту от корня найдёт все связанное. Если разработчик это не сделал или не смог разобраться и реализовал своё, хотя уже было, то это джун. Архитектура от джунов не поможет.
Код дублируется... Либо запутывается от сервиса к сервису...
SOA тоже надо уметь использовать.
Если дублируется, то разработчик не нашёл уже имеющуюся реализацию , это к пункту 1, это не к архитектуре.
Если запутывается в инжекте, то нужно общее правило написать в ридми в корне репы - бизнес логику функционала расширять внутри сервиса, а не снаружи (это про S из солида, и агрегаты из DDD).
Представьте, как будет выглядеть этот if для 10 обязательных полей
Так же, как и с логикой в сущности. Если вам надо проверить 10 полей, значит вам надо проверить 10 полей.
часто в такие сервисы с логикой тащат инфраструктурные зависимости, такие как доктриновский EntityManager
При таком подходе код становится существенно грязнее
При таком подходе единственная разница будет в том, что вместо $this->transactionalSession будет $this->entityManager.
Код какого из вариантов чище и лаконичнее, судите сами
Угу, в одном из вариантов молча заменили проверки на один вызов $client->profileComplete(), код которого не привели, и предлагаете сравнивать.
С конструкторами тоже, логика в сервисах не запрещает делать конструктор с параметрами.
Последовательная логика из бизнес-требований теперь разбросана по разным местам.
Что за "accountActivated", какой части бизнес-требований он соответствует?
Открываем метод "BonusAccount::activate()", где там сохранение в истории факта активации бонусной программы?
А-а, это оказывается одно и то же. Потратили много времени просто чтобы найти где что. А в сервисе это написано прямым текстом. Особенно если вынести эти шаги в private-методы с понятными названиями.
Первый минус, который из этого вытекает, состоит в том, что вам придется аккумулировать и контролировать знания о том, а какие в проекте есть сервисы с логикой.
А с логикой в сущностях вам придется аккумулировать и контролировать знания о том, какие в проекте есть сущности с логикой.
то легко возникнет ситуация, когда в рамках новой логики бонусный счет может быть активирован без соблюдения уже существующих правил: обязательного заполнения профиля и записи события активации в историю.
Если разработчик, взявший новую задачу в работу, не узнал про метод BonusAccount::activate() (например потому что их там несколько десятков) или не увидел его в структуре проекта, то легко возникнет ситуация, когда в рамках новой логики бонусный счет может быть активирован без соблюдения уже существующих правил: обязательного заполнения профиля и записи события активации в историю.
Чтобы включить старую бизнес-логику в новую, вам придется сделать одно из двух: либо продублировать код со всеми вытекающими, либо запутать код, притащив BonusAccountService в зависимости нового сервиса, и вызывая его там.
Ни то ни другое. Во-первых, новый метод активации скорее всего надо будет добавить в BonusAccountService. Точно так же, как вы будете их добавлять в BonusAccount, из-за чего она постепенно превратится в God-object.
Во-вторых, у вас есть новые бизнес-требования, они могут содержать совершенно другую логику активации. С одним сервисом общие части можно вынести в private-методы, с разными в отдельный общий сервис, и вызывать их в нужном порядке. Вызывать один сервис верхнего уровня из другого неправильно.
Заключение и резюме
А, это все минусы? Ну ок.

Анемичные модели с логикой в сервисах: плюсы и минусы одного из самых популярных подходов к разработке на PHP