Денис Цветцих - Rich Model и Anemic Model: враги или друзья
HTML-код
- Опубликовано: 30 сен 2024
- Подробнее о конференции DotNext: jrg.su/3WmFRE
- -
В сообществе много холиваров о том, какую модель лучше использовать - Rich или Anemic. При этом лагерь консультантов активно топит за Rich Model, а среди разработчиков популярна Anemic Model. Поговорим о модели без предрассудков и с практической точки зрения. Для начала определимся с понятиями, что принято называть терминами Rich Model и Anemic Model, по каким критериям их можно отличить. Посмотрим, стоит ли использовать две модели данных: бизнес-модель для бизнес-логики и отдельную data-модель для маппинга на базу. Увидим, какие кейсы невозможно реализовать в канонической Rich-модели. Обсудим, так ли страшна анемичная модель, как ее ругают. И придем к прагматичной модели, которая чаще всего встречается на практике.
По DDD есть ряд замечаний.
Делать персональные ВО для всех полей для избежания ошибочного присвоения - очевидная глупость, не знаю кто и где так советует делать. Такое имеет смысл только для ID, типов которых будет не много, а шанс на ошибку и ее критичность большие. VO прекрасное место для размещения методов валидации, которые могут быть вызваны в application слое при построении этих самых VO(например, в VO могут быть фабричные методы, порождающие этот VO на основе более примитивного типа или возвращающие ошибку валидации).
О том, что инфраструктура хранения плохо работает с ВО - если говорить о ORM типа EF, да, ИНОГДА бывает сложновато, но всегда решаемо. Да и хранилище не всегда relational database за EF, это может быть relational database без EF, с EF и отдельной persitence моделью, document DB, kev-value db или вовсе event store, где такой проблемы может и не быть или она уже и так решается иным образом.
По агрегатам - задача агрегата определить границы строгой консистентности. Что с агрегатом, что без - исполнения этого бизнес требования по строгой консистентности несет неизбежные расходы по производительности.
С примера по заказу: если смена одного orderItem не требует обновления всего заказа или позволяет это сделать с eventual consistency - то границы агрегата с примера выбраны неверно. Если не позволяет - то подход с обновлением одного orderItem принесет когда-то кому-то баг и создаст уязвимость вроде покупки 1М товаров по стоимости одной штучки)). Вероятно, понимание автором концепции агрегата искажено, если возникает постоянное желание как-то их частично редактировать, или редактировать несколько в одной транзакции. События - это часть агрегата (тот же подход event sourcing, сохраняет только эту часть, так как состояние можно восстановить), по этому их сохранения в одной транзакции с состоянием не нарушает это правило, а наоборот его подтверждает. Вся пляска с Outbox - проблема инфраструктуры, а не домена, вызванная желанием хранить состояние и события в разных хранилищах, между которыми нет стандартного механизма распределенных транзакция.
Пример с патчем крайне странная вещь. Если в домене есть понятия установки свойств моделируемой сущности, то ничто не мешает сделать их сеттеры публичными. DDD не запрещает публичные сеттеры, просто не так уж и часто в домене есть операция - установить свойство. Обычно домен имеет более специальные операции, следствием которых есть изменения значения свойств модели. Так же модель может и метод ApplyPath поддерживать, и это никак не нарушит инкапсуляцию, если и правда в данной предметной области есть такое действие. Это не запреты в DDD, это маркеры для обращения внимания, ибо ЧАСТО наличие такого говорит о слабом понимании предметной области и больших серых зонах, которые закрываются в такой способ и за что будет позже расплата. Ну или о слишком простой модели, где DDD просто не нужно, как и разработка отдельного программного продукта, и где задачу проще решить универсальной noCode платформой.
Главный мой совет - не стоит заниматься Карго DDD, продолжая мыслить на Transaction Script манер. Если TS позволяет решать ваши задачи хорошо - вам не нужен DDD. DDD- это инструмент борьбы с доменной сложностью, для применения которого должна быть эта самая сложность и умение его применять у ВСЕЙ команды проекта. Применять разные практики с DDD частично - также возможно, главное делать с умом. Никто не запрещает применять стратегические паттерны вроде единого языка, ограниченных контекстов и способов их интеграции и при этом внутри контекста не применять тактические паттерны и использовать TS подход. И наоборот, никто не мешает в монолитном приложении использовать агрегаты и ВО. Главное понимать суть этих шаблонов и применять их там, где они уместны.
Пример с жирным аггрегатом в виде заказа как бы намекает на то, что аггрегат, возможно, выбран не самым правильным образом. У аггрегата лишь одна задача - обеспечение транзакционной целостности множества сущностей. Если можно изолированно менять строку заказа, то аггрегат - это именно она. А дальше уже начинаются пляски, если существуют бизнес-правила для всего заказа целиком. Тут придется выбирать, или все-таки считать аггрегатом весь заказ и решать проблемы производительности иными способами, или городить огород с сагами, синхронной обработкой событий, откатом изменений (а это сложно, дорого, и вся операция все равно будет длинной во времени).
По мне уж лучше медленный, но корректный код, чем быстрый, но с шансом, что он какие-то бизнес инварианты пропускает.
P.S. Спасибо за до клад. Есть над чем подумать. ;-)
Ну как-то... не знаю. Постоянно возникает ощущение, что любую из двух моделей (anemic vs rich) критикуют люди, плохо в ней разобравшиеся. Ибо обе - прекрасно работают в прямых руках.
Например - критика value objects на основе тезиса о "невозможности фильтрации по полям" вроде как упускает реальные возможности EF, в частности - OwnsOne, всё там можно.
Критика агрегатов на основе тезиса "в заказе слишком много позиций" как бы сходу ставит под сомнение И логику выделения такого "жирного" Агрегата вообще И понимание докладчиком самой сути этого паттерна, а суть в том, что если "просто изменить одну позицию заказа", то у вас может "просто отъехать вычисление скидки, кешбека и т.п.".
Если Order и его OrderItem-ы не атомарны, так и нефиг их объединять в один Агрегат.
В целом, современная разработка в рамках DDD на тактическом уровне - это постоянное балансирование между атомарностью Агрегата и распределённостью логики посредством Domain Events (с синхронизацией изменений различных агрегатов посредством транзакций).
Позиция и изыскания автора объясняются тем что он сразу загнал себя в угол одной универсальной моделью без разделения на business entity и data entity и попытался на нее натянуть все консёрны одним махом
Прошу Автора посмотреть доклад Владимира Хорикова про Валидацию в ДДД про объекты значения, там вопрос на 27 минуте в этом видео полностью и детально рассмотрен. Я думаю автор получит удовольствие от просмотра и ответы на вопросы заявляенные в этом видео.
Нельзя сидеть на двух стульях, а именно пытаться сделать вид, что на сущностях из базы данных можно реализовать ддд.
Если и возможно - то только при таком низком уровне сложности модели, где и без ДДД можно, а скорее, и вовсе без написания кода решить эту задачу в раз 10 дешевле используя (no/low)Code сервис.
Я как-то упустил момент во времени: когда это публичный сеттер стал нарушением инкапсуляции? Свойства для инкапсуляции изначально и задуманы (это же просто 2 метода, ну и + поле под ними, если это автопроперти). Звучит как какой-то нонсенс. Если нужно задать значение извне, то он обязан был публичным, не нужно для него городить отдельный метод (он сам собой именно им и является).
Если задание любого значения свойству не нарушает инварианты сущности - оно спокойно может обладать паблик сеттером и модель при этом не прекратит быть рич.
Мне нравится тема разделения модели и поведения. И предложенный способ валидации на уровне проекта (чтобы в сеттеры модели лазить только из одного проекта) выглядит очень красиво. А вариант когда модель нашпиговывают всеми имеющими отношение к модели методами выглядит не как Rich а скорее как Big Ball of mud. Особенно это заметно присложных многострочных методах
Про размер агрегата можно почитать Вон Вернона красную книгу, там обсуждаются размеры. Проблема в том, что они считают что агрегат это граница транзакционной согласованности в первую очередь, и чем больше агрегат, тем внутри него должны быть более изощрённые правила согласования кластера объектов, которыми и обоснован размер. Как правило один Агрегат соответствует одной сущности. Плюс в большинстве систем эти агрегаты собираются из событий предметной области и лежат в памяти как целый агрегат. Пол книги автор рассказывает про распределённоые системы. Идея в том, что у вас может быть много серверов с частью агрегатов, а не один сервер и база на петобайты.
Проблема в том, что инкапсуляция - контекстное понятие, но языки программирования такую штуку практически никак не поддерживают. Тот же ORM болт кладет на инкапсуляцию и лезет в private члены entity или использует private конструктор.
JsonPatch - это про REST, но DDD больше про команды, а не про ресурсы. Если клиенту приехала DTO-портянка заказа со всеми строками (read model), это не значит, что обязан существовать endpoint, который точно такую же DTO ест.
Спасибо, провели хороший ресёрч, очень ценный доклад!
Очень комплексно рассказано, спасибо!
Вода, кругом вода, больше водыыы
Мне одному кажется, что все проблемы Rich-модели были бы решены, если в c# добавить понятие дружественных классов, которое есть в с++?
Они уже есть. Nested классы имеют доступ к private родителя.
Вероятно вы ищите способ как поделиться внутренней реализацией, с определённым классом, это довольно просто можно сделать с помощью делегатов
Спасибо за видео!!!
По имени!