DDD, сервисные слои и вопросы
Добавлено: 2018.12.18, 16:12
Пытаюсь встроить текущие процессы в рамки DDD и возникают вопросы. Помогите разобраться, пожалуйста.
Есть обычный магазин с обычными сущностями: Item (id, title, quantity), Order(id, status, userId) , OrderItem (id, orderId, quantity, price), и с двусторонней синхронизацией со внешней CRM (обмениваемся каталогами, складами и заказами).
1. Как правильно встроить многоступенчатые (лог -> email -> смс) уведомления о смене статуса заказа? Вопрос скорее относиться к тому как правильно работать с декораторами?
Например у модели Order есть метод changeStatus($newStatus) и внутри вызывается событие OrderUpdateStatusEvent, вызываемый внутри оного. Так же объявляем интерфейс сервиса NotificationServiceInterface,
1.1 Как будет правильно вызвать этот метод?
- внутри changeStatus (надо еще придумать как, чтобы было без протечки) обратиться к сервису
- где-нибудь отдельно подвязываться к событию и сделать обработчик на событие и в нем обратиться к сервису
1.2. Как будет правильно реализовать сами уведомлени?
- в одной реализации сервиса описать все методы? (вариант на самом деле не очень, поэтому перехожу ко второму)
- сделать через декораторы: один класс email, другой sms, итд. Вопрос в том как сделать вызов всех существующих декораторов и еще не написанных (например потом захотели бы во внешнеюю систему отправить)
2. Синхронизация с внешней подсистемой. Как изменять данные снизу-вверх, и как недопустить зацикливания?
Здесь относительно вроде понятно. Сам код относится к инфраструктуре, подписывается на события связанные с заказами и отправляет данные куда надо.
Например, мы можем поменять заказ через Order->updateOrder($updateOrderDto) (со всей сопутствующей цепочкой). Теперь мы меняем заказ во внешней системе, и шлем данные обратно к нам.
2.1 Как избежать цикла?
Мы пытаемся обновить заказ через updateOrder -> он отправляет событие -> мы отправляем данные опять в crm. Где поставить флаг, что надо пропустить обработку?
2.2. Как пересчитать остатки на складе?
В нашей системе есть модуль склада, к которому обращается модуль магазина с проверками наличия товара, а также с вызовами на предмет пересчета. Сам склад событий не имеет.
Получается чтобы не потерять данные мы всегда должны обращаться только сверху-вниз, т.е через заказы, но никогда нельзя напрямую к тем же складам, верно?
То есть, например, в crm обновилось количество товара в одном из заказов - мы все равно должны поменять их через заказ, если не предусмотрено иное, верно?
3. Хранение доп.данных и репозитарии.
3.1 как быть с CrmExternalId
Для синхронизации с внешней подсистемой мы запоминаем externalId для заказов и товаров.
Как и куда положить этот externalId?.
Обычно без ddd, привычнымы нажатиями клавиш вносим externalId в таблицы с сущностями, добавить поля к моделям и продолжить работать как ни в чем не бывало.
Как быть здесь? вроде проблема решается отдельными таблицами: ItemExtrenalCrm (itemId,externalCrm) итд, но когда надо например обновить полный каталог товаров из нескольких десятков тысяч позиций как мы должны обращаться к ним? Репозитарий Item не знает о внешних данных. Ладно можно джойнить когда относительно немного строк в таблицах. Но как это дело оптимизировать когда нужна будет скорость?
3.2 Когда сохранять данные?
Есть сервис редактирования заказа (OrderUpdateService), который обращается к одному из заказов через updateOrder. И внутри метода мы, например, пересчитываем скидки по товарам в заказе. Где должно быть сохранение? В сервисе или в методе модели? По идее в сервисе, который знает о репозитории и может вызвать метод сохранения там.
Тогда как как должен обращаться к заказу и сохранять заказ модуль синхронизации с crm?
- Вызывать сервис и через dto отправлять данные?
- Обращаться самостоятельно к репозитарию, менять данные и вызывать сохранение?
Вопрос в том как в какой-то момент, например, поменять статус заказа через его метод и не забыть сохранить его?
В книге Domain-Driven Design in PHP by Carlos Buenosvinos, Christian Soronellas, and Keyvan Akbary (https://leanpub.com/ddd-in-php) люди используют доктрину, но у меня еще нет понимания как это работает.
Есть ли способы проще?
4. Шина команд и обработка исключений
тут шина команд обсуждалась с хорошими примерами в viewtopic.php?f=34&t=36725&start=60#p188542
Вопрос в том как ловить исключения? Контроллер должен объявлять try catch со списком всех возможных исключений и портянкой на это дело? или есть способ попроще?
Есть обычный магазин с обычными сущностями: Item (id, title, quantity), Order(id, status, userId) , OrderItem (id, orderId, quantity, price), и с двусторонней синхронизацией со внешней CRM (обмениваемся каталогами, складами и заказами).
1. Как правильно встроить многоступенчатые (лог -> email -> смс) уведомления о смене статуса заказа? Вопрос скорее относиться к тому как правильно работать с декораторами?
Например у модели Order есть метод changeStatus($newStatus) и внутри вызывается событие OrderUpdateStatusEvent, вызываемый внутри оного. Так же объявляем интерфейс сервиса NotificationServiceInterface,
1.1 Как будет правильно вызвать этот метод?
- внутри changeStatus (надо еще придумать как, чтобы было без протечки) обратиться к сервису
- где-нибудь отдельно подвязываться к событию и сделать обработчик на событие и в нем обратиться к сервису
1.2. Как будет правильно реализовать сами уведомлени?
- в одной реализации сервиса описать все методы? (вариант на самом деле не очень, поэтому перехожу ко второму)
- сделать через декораторы: один класс email, другой sms, итд. Вопрос в том как сделать вызов всех существующих декораторов и еще не написанных (например потом захотели бы во внешнеюю систему отправить)
2. Синхронизация с внешней подсистемой. Как изменять данные снизу-вверх, и как недопустить зацикливания?
Здесь относительно вроде понятно. Сам код относится к инфраструктуре, подписывается на события связанные с заказами и отправляет данные куда надо.
Например, мы можем поменять заказ через Order->updateOrder($updateOrderDto) (со всей сопутствующей цепочкой). Теперь мы меняем заказ во внешней системе, и шлем данные обратно к нам.
2.1 Как избежать цикла?
Мы пытаемся обновить заказ через updateOrder -> он отправляет событие -> мы отправляем данные опять в crm. Где поставить флаг, что надо пропустить обработку?
2.2. Как пересчитать остатки на складе?
В нашей системе есть модуль склада, к которому обращается модуль магазина с проверками наличия товара, а также с вызовами на предмет пересчета. Сам склад событий не имеет.
Получается чтобы не потерять данные мы всегда должны обращаться только сверху-вниз, т.е через заказы, но никогда нельзя напрямую к тем же складам, верно?
То есть, например, в crm обновилось количество товара в одном из заказов - мы все равно должны поменять их через заказ, если не предусмотрено иное, верно?
3. Хранение доп.данных и репозитарии.
3.1 как быть с CrmExternalId
Для синхронизации с внешней подсистемой мы запоминаем externalId для заказов и товаров.
Как и куда положить этот externalId?.
Обычно без ddd, привычнымы нажатиями клавиш вносим externalId в таблицы с сущностями, добавить поля к моделям и продолжить работать как ни в чем не бывало.
Как быть здесь? вроде проблема решается отдельными таблицами: ItemExtrenalCrm (itemId,externalCrm) итд, но когда надо например обновить полный каталог товаров из нескольких десятков тысяч позиций как мы должны обращаться к ним? Репозитарий Item не знает о внешних данных. Ладно можно джойнить когда относительно немного строк в таблицах. Но как это дело оптимизировать когда нужна будет скорость?
3.2 Когда сохранять данные?
Есть сервис редактирования заказа (OrderUpdateService), который обращается к одному из заказов через updateOrder. И внутри метода мы, например, пересчитываем скидки по товарам в заказе. Где должно быть сохранение? В сервисе или в методе модели? По идее в сервисе, который знает о репозитории и может вызвать метод сохранения там.
Тогда как как должен обращаться к заказу и сохранять заказ модуль синхронизации с crm?
- Вызывать сервис и через dto отправлять данные?
- Обращаться самостоятельно к репозитарию, менять данные и вызывать сохранение?
Вопрос в том как в какой-то момент, например, поменять статус заказа через его метод и не забыть сохранить его?
В книге Domain-Driven Design in PHP by Carlos Buenosvinos, Christian Soronellas, and Keyvan Akbary (https://leanpub.com/ddd-in-php) люди используют доктрину, но у меня еще нет понимания как это работает.
Есть ли способы проще?
4. Шина команд и обработка исключений
тут шина команд обсуждалась с хорошими примерами в viewtopic.php?f=34&t=36725&start=60#p188542
Вопрос в том как ловить исключения? Контроллер должен объявлять try catch со списком всех возможных исключений и портянкой на это дело? или есть способ попроще?