@Xveeder

Как решить проблему Rich Model в DDD?

Доброго времени суток. При переходе от анимичной модели к богатой модели происходит перенос связанной с доменом логики из сервисов в саму модель. Это в свою очередь порождает ряд проблем, решение которых для меня не вполне очевидно.

Рассмотрим пример:

У нас есть модель пользователя, которая включает в себя объект скидки. В базе скидка зависит от уровня пользователя, который в свою очередь зависит от количества покупок.

В богатой модели, доступ к атрибутам модели закрыт и доступны лишь интерфейсы, содержащие бизнес логику. В данном кейсе, есть метод addOrder, при запуске которого при достижении определенного значения апается уровень, а при апе уровня изменяется размер скидки. Все круто, модель инкапсулирована. И все в рамках DDD.

Однако, реальный бизнес склонен изменять поведение системы в соответствии с новыми бизнес-кейсами и вот ПО приходит с новым запросом: хотим добавить промо-коды на скидку. А через месяц добавляется новое условие: добавить апдейт скидки также по сумме заказов. А еще через 2 месяца, новое условие: пользователям с подпиской устанавливаем свой уровень скидок. Еще через месяц добавили уровни подписок и соответственно, на каждом уровне подписки своя скидка.

Также, подписки находятся в отдельном микросервисе, имеют свою БД и никак не связаны с сущностью пользователь.

При этом, у нас есть иерархия установки скидок, например, при наличии подписки, другие методы расчета не работают.

Вопрос: Как при таких условиях сохранить инкапсуляцию в богатой модели?
Постоянно расширять интерфейс? Делать класс бога, который отвечает за все? Но как в таком случае реализовывать SRP, DI и IS?

В анимичной модели все достаточно просто, у нас есть набор сервисов, которые манипулируют сущностью. Интерфейс сущности открыт и сервис может управлять сущностью в соответствии с бизнес-процессом. Мы можем добавлять новые сервисы, которые будут запускаться в рамках различных бизнес-процессов. Мы можем создать конвейер сервисов, обслуживающих определенный процесс и динамически им управлять.

Богатый домен лишает нас этой динамики, поскольку, например, он не может смотреть на слой инфраструктуры, хотя в рамках бизнес-кейсов у нас может быть зависимость на БД или внешних сервисах.

Например, в сервисе подписки произошел ивент addNewSubscribe, при этом ивенте необходимо обновить размер скидки, но если у нас богатая модель, то интерфейс setDiscount отсутствует, так как пропсы модели закрыты.

P.S. Я понимаю, что скидку можно вынести в отдельную модель, декомпозировав сущность и разделив ответственность. Это просто упрощенный пример.
  • Вопрос задан
  • 166 просмотров
Пригласить эксперта
Ответы на вопрос 3
AshBlade
@AshBlade
Просто хочу быть счастливым
Отвечу на вопрос так: ядро DDD - представление бизнес-логики в коде. Добавляются промокоды, уровни скидок, подписки и т.д. - все это меняет бизнес-логику. Это нельзя просто просто взять и убрать, т.к. грубо говоря, под эти дела завели отдельные документы (юридически оформили уровни скидок, промокоды и т.д.). Соответственно и твоя доменная сущность должна измениться, так как она - отражение реального мира, а реальный мир изменился.

В секции "Вопрос: ..." у тебя 3 вопроса, но смысл у них одинаковый: как правильно писать код так, чтобы было легко добавлять изменения?
На абстрактный вопрос отвечу абстрактно - зависит. Для каждого случая надо искать свое решение, нет единого правильного, в любом случае тебе будет нужно изменять код, чтобы соответствовать условиям реального мира.

P.S. сделаю замечание по поводу этой фразы:

Также, подписки находятся в отдельном микросервисе, имеют свою БД и никак не связаны с сущностью пользователь.


Тут явно это не говорится, но мне кажется, что ты разделяешь микросервисы по сущностям. Это плохой подход, так как ведет к головным болям, которые приводят к подобному (заданному) вопросу. Запомни: микросервисы надо разделять по бизнес-задачам, а не сущностям (иначе получаются вот такие crud сервисы, знающие друг о друге).
Начни с этой статьи, и копай дальше. Иногда вопросы кода надо решать на уровне архитектуры системы.
Ответ написан
Комментировать
vhood
@vhood
Не забывайте отмечать решения
бизнес склонен изменять поведение системы
Хороший архитектор склонен пересматривать архитектуру

есть модель пользователя, которая включает в себя объект скидки
Судя по тексту ниже, скидки стоит отделить от пользователей

подписки находятся в отдельном микросервисе, имеют свою БД и никак не связаны с сущностью пользователь
Замечательно, значит скидки могут быть в отдельном микросервисе, иметь свою БД и иметь только идентификатор пользователя и идентификатор подписки. Пользователи же, скорее всего, будут в сервисе авторизации
Ответ написан
Комментировать
dmitriylanets
@dmitriylanets
веб-разработчик
DDD это больше про контекст чем про сущности, поэтому ваша модель должна быть в нескольких контекстах с разным поведением. Из описанной модели данных есть пользователь, заказ и скидка.
Если брать контекст заказа, то там заказ будет больше всего иметь атрибутов, поведения и тд, а скидка там будет слаба выражена. Но если перейти в контекст "Калькулятор скидки", то там сущность скидки будет центральной сущностью, остальные сущности будут иметь атрибуты лишь необходимые для данного контекста.
Почему отдельный контекст скидки? потому что чтобы ее рассчитать например не всегда и нужен заказ - скидка от группы пользователя (вип, гость и тд), местоположения, история заказов, категория купленных товаров и тд. В этом и преимущество от Service Layer, так как создав OrderService и метод calculateDiscount может получиться, что о заказе сервис будет знать совсем ничего, а вот от остальных сущностях очень много, а расположение уже определено.
Ответ написан
Комментировать
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Похожие вопросы