DDD: Как отделить модели и логику от базы данных?

Обсуждаем, как правильно строить приложения
Ответить
yujin1st
Сообщения: 192
Зарегистрирован: 2012.03.26, 12:03

DDD: Как отделить модели и логику от базы данных?

Сообщение yujin1st »

Здравствуйте. Есть общий вопрос: как отделить модели и логику от базы данных?
Например, обычный магазин с обработкой заказов. Item, Order, OrderItem
Подскажите, пожалуйста, как ответить на него для двух конкретных ситуаций:

1. Как сделать процесс смены статуса заказа?
Нужно чтобы любой компонент системы (например внешняя CRM) мог обратится к заказу, поменять статус и цепочка запустилась: сохранилось в базу, отправились уведомления и т.д
В AR набросать просто:

Код: Выделить всё

class Order{
  public function changeStatus($newStatus){
    $this->status = $newStatus;
    $this->save(false); // или updateAttributes()
    $this->trigger('statusChanged');
  }
}
А как сделать это в терминологии DDD?
Доменная модель же не должна знать о базе. Кто будет отвечать за сохранение, когда мы вызовем $order->changeStatus('updated') ?

2. Как запрашивать разные связные данные для одних тех же моделей?
Ситуация: Отображение заказа у клиента и у менеджера.
С одной стороны, клиент в своем заказа человек видит товары (с картинками) и сумму. Мы через репозитарий получаем модель заказа и входящие товары.
А с другой менеджер будет видеть закупочную стоимость (представим, что есть цепочка работы со складом: Contractor, ItemContractor (item, price)), будет видеть цену доставки, будет видеть, но ему не нужно описание товара)

То есть модель вроде одна и есть Order, но набор данных для нее разный.
Здесь же другой вопрос: с AR мы можем легко через relation'ы в карточке заказа вывести и информацию о поставщике и его бабушке, а в случае c отдельными репозитариями, получается выборка данных превращается в головоломку.
И самое главное получается что в одной и той же модели в разных контекстах в модели может не быть разных данных?
noLogicOnlyWar
Сообщения: 83
Зарегистрирован: 2017.07.04, 20:53

Re: DDD: Как отделить модели и логику от базы данных?

Сообщение noLogicOnlyWar »

1) В книге ddd in php описывается ровно ваш пример
2) современные датамаперы прекрасно работают с realtion'ами, никакой проблемы нет. Если вы думаете что вам нужен репоризиторий и на Order и на OrderInfoBlablabla то это не так, репозиторий создается для агрегатов в отношение 1 к 1му. В вашем случае у вас только 1 реп для Order.
yujin1st
Сообщения: 192
Зарегистрирован: 2012.03.26, 12:03

Re: DDD: Как отделить модели и логику от базы данных?

Сообщение yujin1st »

Спасибо, что отвечаете =)

1. Да, прочитал книгу и смотрел пример с желаниями. И я не понял как это повторить.
Там повсюду используются сервисы приложения, и в частности в silex'e они и вызываются.
На вопрос, как в доменной модели вызвать событие, которое потом сохранится в базе я не нашел там ответа.
Еще пример, удаление желания - DeleteWishService, обращается к модели User, которая убирает желание из внутреннего массива. Как это попадает в базу?

Я понимаю, что доменный слой не должен знать о базе в принципе, но тогда как реализовать изменение данных и их сохранение?
другая же модель не будет обращаться к сервисам?

2. То есть получается мы делаем агрегаторы ClientOrderCreate, ClientOrderView, AdminOrderViewFull, AdminOrderViewShort на каждый чих и не будет одной модели Order ибо она будет слишком много знать?
noLogicOnlyWar
Сообщения: 83
Зарегистрирован: 2017.07.04, 20:53

Re: DDD: Как отделить модели и логику от базы данных?

Сообщение noLogicOnlyWar »

На вопрос, как в доменной модели вызвать событие, которое потом сохранится в базе я не нашел там ответа
Не очень понятно о чем речь. Если надо сохранить где либо событие то используйте специальный подписчик.
Еще пример, удаление желания - DeleteWishService, обращается к модели User, которая убирает желание из внутреннего массива. Как это попадает в базу?
За это отвечает датамапер, в этой конкретной книге используется doctrine.
2. То есть получается мы делаем агрегаторы ClientOrderCreate, ClientOrderView, AdminOrderViewFull, AdminOrderViewShort на каждый чих и не будет одной модели Order ибо она будет слишком много знать?
Нет у вас один агрегат - order. Думаю вы не совсем понимаете что это. Что такое агрегат есть кратко в ddd in php и более подробно у Вернона.
Я понимаю, что доменный слой не должен знать о базе в принципе, но тогда как реализовать изменение данных и их сохранение?
другая же модель не будет обращаться к сервисам?
Да не должен. Представьте если бы ваши сущности хранились не в бд а например в массиве. У вас есть набор сущностей order. И есть коллекция где вы их храните - массив php. Как вы изменяете сущность order в коллекции? Вы достаете конкретный order изменяете его состояние и все, апдейт есть. Как вы сохраняете новый order в коллекции? делаете $orderCollection[] = $order.
В этом суть абстракции которую дает вам репозиторий. Для доменного слоя прозрачно куда в конечном счете сохранятся ваши сущности - в базу данных, в массив или куда то еще, домен работает с репозиторием который предоставляет интерфейс коллекции как будто наши сущности лежат в памяти.
yujin1st
Сообщения: 192
Зарегистрирован: 2012.03.26, 12:03

Re: DDD: Как отделить модели и логику от базы данных?

Сообщение yujin1st »

noLogicOnlyWar писал(а): 2019.05.16, 15:12 Да не должен. Представьте если бы ваши сущности хранились не в бд а например в массиве. У вас есть набор сущностей order. И есть коллекция где вы их храните - массив php. Как вы изменяете сущность order в коллекции? Вы достаете конкретный order изменяете его состояние и все, апдейт есть. Как вы сохраняете новый order в коллекции? делаете $orderCollection[] = $order.
В этом суть абстракции которую дает вам репозиторий. Для доменного слоя прозрачно куда в конечном счете сохранятся ваши сущности - в базу данных, в массив или куда то еще, домен работает с репозиторием который предоставляет интерфейс коллекции как будто наши сущности лежат в памяти.
Вот это и смущает больше всего. Я не понимаю как выйти из этого доменного слоя в другие.
Все тот же вопрос: Как на $order->updateStatus($newStatus) навешать сохранение в базу. ?

вы написали, но я не понимаю как это должно быть организовано: кто подписывается на событие и в каком месте сама подписка происходит?
noLogicOnlyWar писал(а): 2019.05.16, 15:12Если надо сохранить где либо событие то используйте специальный подписчик.

Тут же вопрос другого рода:
например при смене того же статуса нужно
- отправить данные во внешнюю crm
- изменить стоимость отдельных позиций и пересчитать сумму заказа

В первом случае вроде понятно: внешний компонент подписывается на событие и делает свое дело
Но во втором случае основной вопрос в том, кто подписывается на это событие?


----
noLogicOnlyWar писал(а): 2019.05.16, 15:12 Нет у вас один агрегат - order. Думаю вы не совсем понимаете что это. Что такое агрегат есть кратко в ddd in php и более подробно у Вернона.
Скорее всего так, да, пока еще разбираюсь и перевариваю.

я не понимаю следующий момент со связанными данными:
в одном месте у заказа нужен доступ только к элементам заказа, в другой момент нам нужен доступ к позициям каталога к которым привязан заказ. Кто разруливает получение данных из базы/(апи/любого другого места)? То есть модель orderItem в один момент будет знать о item а в другом моменте нет? Репозитарий с кучей разных методов: OrderRepositary::getShortOrderInfo, OrderRepositary::getOrderWithCatalogItems
noLogicOnlyWar
Сообщения: 83
Зарегистрирован: 2017.07.04, 20:53

Re: DDD: Как отделить модели и логику от базы данных?

Сообщение noLogicOnlyWar »

Вот это и смущает больше всего. Я не понимаю как выйти из этого доменного слоя в другие.
Все тот же вопрос: Как на $order->updateStatus($newStatus) навешать сохранение в базу. ?

вы написали, но я не понимаю как это должно быть организовано: кто подписывается на событие и в каком месте сама подписка происходит?
События тут не причем. За сохранение в базу отвечает orm (почитайте/потрогайте doctrine). Вкратце - навешивать ничего не надо, доктрина следит за сущностями с которыми вы работаете и при вызове flush() закоммитит все ваши изменения в базу.
Тут же вопрос другого рода:
например при смене того же статуса нужно
- отправить данные во внешнюю crm
- изменить стоимость отдельных позиций и пересчитать сумму заказа

В первом случае вроде понятно: внешний компонент подписывается на событие и делает свое дело
Но во втором случае основной вопрос в том, кто подписывается на это событие?
Не надо все моделировать через события.
Почему вы решили что ваш воторой случай должен отрабатывать по событию? Первый - ок, нам ненужна транзакционность, произошла ошибка во время отправки данных - не беда, забьем либо синхронизируем позже. Но будете ли вы довольны если после изменения статуса не пересчиталась сумма заказа? Я думаю врятли, и я думаю это противоречит бизнес правилам. Такая ситуация называется инвариант (сумма должна обязательно измениться) и за его сохранность отвечает агрегат (в вашем случае заказ). Таким образом данный код должен вызываться там же где и меняется статус.
Прочитайте про инвариант, итоговую и транзакционную согласованность, думаю станет понятнее.
Кто разруливает получение данных из базы/(апи/любого другого места)? То есть модель orderItem в один момент будет знать о item а в другом моменте нет?
orm. Дока доктрины вам в помощь, конкретно то о чем вы говорите называется lazy loading.
yujin1st
Сообщения: 192
Зарегистрирован: 2012.03.26, 12:03

Re: DDD: Как отделить модели и логику от базы данных?

Сообщение yujin1st »

noLogicOnlyWar писал(а): 2019.05.24, 01:23
События тут не причем. За сохранение в базу отвечает orm (почитайте/потрогайте doctrine). Вкратце - навешивать ничего не надо, доктрина следит за сущностями с которыми вы работаете и при вызове flush() закоммитит все ваши изменения в базу.
Так все равно та же ситуация и идет: кто-то где-то должен знать о том, что есть база данных и вызвать тот же flush. Или контроллер или сервис. Т.е. по факту любой кто вызовет $order->changeStatus() должен вызывать $em->flush() (https://symfony.ru/doc/current/doctrine.html)

Очевидно, что принципиально неправильно будет использовать вывод: мы должны писать сохранение в базу абсолютно в каждом сервисе и контроллере, чтобы данные сохранились (не важно, используя AR или ORM или что-то еще).

Кто должен вызывать этот $em->flush() в ситуации когда этот changeStatus может вызваться непонятно кем в модуле?

noLogicOnlyWar писал(а): 2019.05.24, 01:23
Прочитайте про инвариант, итоговую и транзакционную согласованность, думаю станет понятнее.
noLogicOnlyWar писал(а): 2019.05.24, 01:23
orm. Дока доктрины вам в помощь, конкретно то о чем вы говорите называется lazy loading.
Спасибо за направление, буду разбираться дальше.
noLogicOnlyWar
Сообщения: 83
Зарегистрирован: 2017.07.04, 20:53

Re: DDD: Как отделить модели и логику от базы данных?

Сообщение noLogicOnlyWar »

Опять же в ddd in php про это написанно насколько я помню в разделе о command bus. Тк flush это вещь инфраструктурная то где конкретно его вызывать зависит от того что вы используете - в мидлварях tactician или zend pipeline или еще где, вобщем это уже решать вам в зависимости от ваших инфраструктурных подробностей.
yujin1st
Сообщения: 192
Зарегистрирован: 2012.03.26, 12:03

Re: DDD: Как отделить модели и логику от базы данных?

Сообщение yujin1st »

Спасибо огромное за ответы, буду дальше разбирать мат. часть. =)

ps: Есть опыт с несколькими большими и долгими проектах на yii и есть понимание узких моментов и что надо делать что-то качественно другое, чтобы их избегать. Пытаюсь читать все до чего могу дотянутся, но в определенных местах мозг просто не хочет выворачивать извилины в нужном направлении, поэтому и появляются такие вопросы, может местами и глупые.
Ответить