Вау, вы же только что сказали, что это поведение объекта и должно быть внутри класса. Теперь уже не поведение?)
Поведение остается внутри объекта, вы не можете обойти эту проверку при изменении состояния объекта. Можно кстати эту функцию оставить и внутри класса сделать ее публичной и вызывать ее из UI если вам так больше нравиться.
Что еще за "outside", в бизнес-требованиях нет такого термина) Потом этот код открывает другой программист и полчаса разбирается, чему в бизнес-требованиях оно соответствует. Что там было про Ubiquitous language?
Там также нет и термина репозиторий, фабрика, сервис и многих других, что не мешает их использовать в слое домена. Outside это враппер который абстрагирует внешние зависимости агрегата, если интересно тут этот подход описан подробнее https://habr.com/ru/articles/799019/ Если вам так будет понятнее можно назвать метод getOverdraftAmountFromClientAgreement
Есть код, там меньше 300 строк логики на все методы, напишите хотя бы 2 полностью вместе с контроллером и валидацией, тогда будем сравнивать
Не вижу смысла обсуждать такие тривиальныt вещи как слой UI в контексте обсуждение богатой и анемичной модели.
Метод setName() работает с состоянием, значит это поведение
Инфраструктурного объекта возможно, но никак не доменного.
Для UI нужна информация, а ее получение это бизнес-логика. Информация о том, почему переводить нельзя, зависит от бизнес-требований к переводу.
Как уже заметил @powerman не в контексте DDD и богатой модели. В DDD бизнес логикой считается только та логика которая происходит в момент изменения состояния системы. Если вам нужно это состояние получить до изменения, вы используете запрос а не команду.
Ага, клево. Теперь бизнес к вам приходит и говорит "Сделайте мне овердрафт, лимит которого устанавливается в договоре". Что вы будете делать, пробрасывать Agreement в withdraw и isLessThan? Что там ООП говорит про одну причину для изменения?
<?php
final class Account
{
private $blocked = false;
private Money $balance;
private OutsideInterface $outside;
public function withdraw(Money $amount): void
{
if ($this->balance->isLessThan($this->getAvailableAmount())) {
throw new InssuficientFundsException();
}
if ($this->blocked) {
throw new AccountBlockedException();
}
$this->balance = $this->balance->subtract($amount);
}
private function getAvailableAmount() {
return $this->balance->add($this->outside->getOverdraftAmount($this->id));
}
}
Другой вопрос, бизнес к вам приходит и говорит "Сделайте чтобы в форме перевода показывались ошибки сразу от обоих аккаунтов". Что вы будете делать, дублировать валидацию без эксепшенов?
Это уже требования UI а не бизнес логики. В этом случае чтобы избежать дублирования, логику проверки вынесу в чистую функцию, и буду вызываеть ее изнутри доменного объекта и в тех местах UI где нужна такая валидация:
Нет, поведение по управлению состоянием объекта находится в объекте.
Это не поведение, в контексте бизнес логики, с таким же успехом я могу сделать все эти свойства публичными. Вот поведение:
<?php
final class Account
{
private $blocked = false;
private Money $balance;
public function withdraw(Money $amount): void
{
if ($this->balance->isLessThan($amount)) {
throw new InssuficientFundsException();
}
if ($this->blocked) {
throw new AccountBlockedException();
}
$this->balance = $this->balance->subtract($amount);
}
}
Потому что в данном случае ООП тоже используется в нужном объеме.
В слое бизнес логики не используется.
Кроме того, это было сказано как технический недостаток и неправильная модель предметной области, с чем я не согласен
Не правда, я лишь указал на то что это не ООП. Процедурный подход оправдан в некоторых случаях, но для ограниченных контекстов со сложной бизнес логикой ООП подходит гораздо лучше. Для этой цели оно собственно и было придумано, для борьбы со сложностью.
Еще раз: ООП это про объединение состояния и поведения. Если у вас состояние в одном объекте (в анемичной модели), а поведение в другом (в сервисе), то вы нарушаете основной принцип ООП и по факту используете процедурное программирование. В каком месте вы это делаете, в слое доменной логики или в слое инфраструктурных библиотек, совершенно не важно.
Процедурная парадигма тут ни при чем, у меня в коде используются объекты.
Ваши "объекты" это структуры данных и библиотеки процедур. Объект в понимании ООП это объединение состояния и поведения, чего а анемичной модели не наблюдается.
А где я писал что вся бизнес логика должна находиться в одном объекте? Я лишь говорил что она должна находиться внутри объекта оспаривая ваши тезисы:
чтобы изначально делать такую архитектуру, где бизнес-логика реализуется вне доменных объектов)
Объекты доменной модели вообще ничего не знали про логику,
И тут многие идут "упрощением", втаскивая бизнес-логику внутрь объекта
И во множестве реальных ситуация рано или поздно становится понятно, что бизнес-правила - это одном, а низкоуровневая программная модель - это другое.
А я в описанном примере вижу другую проблему. В приложении есть слой для объектов с состоянием, но нет слоя для бизнес-логики. Т.е. проблема возникла тогда, когда бизнес-логику втащили с объект доменной модели.
Вы пытаетесь усложнить задачу, добавляя дополнительные шаги и усложняя алгоритм, но это не изменяет факта, что разбив это алгоритм на серию команд, обработку бизнес логики каждой из этих команд можно поместить внутрь метода соответствующего доменного объекта. Снаружи будет только оркестратор, который знает какую команду в ответ на какое событие запустить, но ничего не знает о внутренних алгоритмах этих команд.
Ок, в заявку добавляется идентификатор продукта, заявка создается, публикует событие что она создана, на это событие триггерится создание доменного объекта "оценка кредитного риска", состояние которого изменяется (и контролируется самим объектом) в ходе проверки, после его завершения публикуется событие, проверка пройдена или не пройдена, это событие слушает заявка и подтверждается либо отклоняется. Все логика самих проверок по прежнему находится внутри доменных объектов.
Вы имеете в виду само значение 80% процентов? Если оно дублируется в нескольких проверках то вынесу его в константу, или в какой то конфиг, и назову maxAcceptableLoanBurden.
К сожалению пока не имел опыта работы с highload, но в теории если применить CQRS и убрать ORM из операций чтения, то это может увеличить производительность, если конечно highload не касается операций записи. Но тогда получается что DDD вообще не применим к highload.
Согласен без DDD такое реализовать проще, но это достаточно специфический случай, я скорее сравнивал классический анемик, в котором сущности вытаскиваются из базы через ORM, сервис манипулирует состоянием этих сущностей через геттеры сеттеры и потом мапит эти сущности через тот-же ORM в БД. Мне кажется что при таком подходе у DDD все же есть преимущество по сравнению с анемичной моделью, даже если приходится изменять состояние нескольких агрегатов в одной транзакции.
Поведение остается внутри объекта, вы не можете обойти эту проверку при изменении состояния объекта. Можно кстати эту функцию оставить и внутри класса сделать ее публичной и вызывать ее из UI если вам так больше нравиться.
Там также нет и термина репозиторий, фабрика, сервис и многих других, что не мешает их использовать в слое домена. Outside это враппер который абстрагирует внешние зависимости агрегата, если интересно тут этот подход описан подробнее https://habr.com/ru/articles/799019/
Если вам так будет понятнее можно назвать метод
getOverdraftAmountFromClientAgreement
Не вижу смысла обсуждать такие тривиальныt вещи как слой UI в контексте обсуждение богатой и анемичной модели.
Инфраструктурного объекта возможно, но никак не доменного.
Как уже заметил @powerman не в контексте DDD и богатой модели. В DDD бизнес логикой считается только та логика которая происходит в момент изменения состояния системы. Если вам нужно это состояние получить до изменения, вы используете запрос а не команду.
Это уже требования UI а не бизнес логики. В этом случае чтобы избежать дублирования, логику проверки вынесу в чистую функцию, и буду вызываеть ее изнутри доменного объекта и в тех местах UI где нужна такая валидация:
Мы говорим о ООП в контексте бизнес логики, а то поведение что вы продемонстировали никакого отношения к бизнес логике не имеет.
Это не поведение, в контексте бизнес логики, с таким же успехом я могу сделать все эти свойства публичными. Вот поведение:
Чтобы уменьшить сложность и не нарушать границы согласованности транзакций, но это вовсе не означает что агрегаты должны быть анемичными.
Плохо читали теорию DDD. Один агрегат не может напрямую управлять состоянием другого.
Так я и говорю, состояние в одном месте, поведение в другом. Процедурное программирование.
Является, потому что аккаунт должнен контролировать свое состояние сам: достаточно ли у него средств для снятия, не заблокирован ли он и т.п.
В слое бизнес логики не используется.
Не правда, я лишь указал на то что это не ООП. Процедурный подход оправдан в некоторых случаях, но для ограниченных контекстов со сложной бизнес логикой ООП подходит гораздо лучше. Для этой цели оно собственно и было придумано, для борьбы со сложностью.
Тогда почему вы не хотите признать, что используете процедурное программирование, а не ООП? :)
Проверка можно ли из статуса "Ожидает оплаты" перейти в статус "Доставлен" где по вашему должна происходить?
Еще раз: ООП это про объединение состояния и поведения. Если у вас состояние в одном объекте (в анемичной модели), а поведение в другом (в сервисе), то вы нарушаете основной принцип ООП и по факту используете процедурное программирование. В каком месте вы это делаете, в слое доменной логики или в слое инфраструктурных библиотек, совершенно не важно.
Ваши "объекты" это структуры данных и библиотеки процедур. Объект в понимании ООП это объединение состояния и поведения, чего а анемичной модели не наблюдается.
Ок, раз вы согласны с моими примерами, значит дискутировать дальше особого смысла нет. Похоже мы просто немного по разному смотрим на одно и тоже.
А где я писал что вся бизнес логика должна находиться в одном объекте? Я лишь говорил что она должна находиться внутри объекта оспаривая ваши тезисы:
Вы пытаетесь усложнить задачу, добавляя дополнительные шаги и усложняя алгоритм, но это не изменяет факта, что разбив это алгоритм на серию команд, обработку бизнес логики каждой из этих команд можно поместить внутрь метода соответствующего доменного объекта. Снаружи будет только оркестратор, который знает какую команду в ответ на какое событие запустить, но ничего не знает о внутренних алгоритмах этих команд.
Ок, в заявку добавляется идентификатор продукта, заявка создается, публикует событие что она создана, на это событие триггерится создание доменного объекта "оценка кредитного риска", состояние которого изменяется (и контролируется самим объектом) в ходе проверки, после его завершения публикуется событие, проверка пройдена или не пройдена, это событие слушает заявка и подтверждается либо отклоняется. Все логика самих проверок по прежнему находится внутри доменных объектов.
Вы имеете в виду само значение 80% процентов? Если оно дублируется в нескольких проверках то вынесу его в константу, или в какой то конфиг, и назову maxAcceptableLoanBurden.
К сожалению пока не имел опыта работы с highload, но в теории если применить CQRS и убрать ORM из операций чтения, то это может увеличить производительность, если конечно highload не касается операций записи. Но тогда получается что DDD вообще не применим к highload.
Согласен без DDD такое реализовать проще, но это достаточно специфический случай, я скорее сравнивал классический анемик, в котором сущности вытаскиваются из базы через ORM, сервис манипулирует состоянием этих сущностей через геттеры сеттеры и потом мапит эти сущности через тот-же ORM в БД. Мне кажется что при таком подходе у DDD все же есть преимущество по сравнению с анемичной моделью, даже если приходится изменять состояние нескольких агрегатов в одной транзакции.
А какое преимущество в данном случае нам даст анемичная модель с transaction script если наши агрегаты достаточно небольшие?