Короткий ответ
Богатая модель инкапсулирует инварианты и поведение рядом с данными, что облегчает их защиту от нарушения и тестирование объекта в изоляции, но рискует разрастись до объекта с слишком многими ответственностями и скрытыми зависимостями; анемичная модель с отдельными сервисами проще комбинировать и переиспользовать для разных сценариев обработки, но легко приводит к размытой логике, дублирующейся в нескольких сервисах, и к данным, которые можно привести в несогласованное состояние, если не все пути проходят через нужные проверки.
Как это работает подробнее
В богатой модели методы, изменяющие состояние объекта (order.cancel(), order.add_item(item)), сами проверяют инварианты (нельзя отменить уже отправленный заказ, нельзя добавить товар в оплаченный заказ) — любой код, вызывающий эти методы, автоматически получает защиту от некорректных переходов состояния, потому что проверка находится в одном месте, а не размазана по вызывающему коду. В анемичной модели Order — это, по сути, dataclass с полями, а проверки и переходы состояния реализованы в отдельных функциях/сервисах (OrderService.cancel(order)), которые сами решают, что можно, а что нет — если появляется второй сервис, изменяющий тот же объект без тех же проверок, инвариант можно случайно нарушить в обход первого сервиса.
Пример кода
# Богатая модель: инвариант защищён внутри объекта
class RichOrder:
def __init__(self, status="new"):
self.status = status
def cancel(self):
if self.status == "shipped":
raise ValueError("Нельзя отменить уже отправленный заказ")
self.status = "cancelled"
# Анемичная модель: данные отдельно, логика — в сервисе
class AnemicOrder:
def __init__(self, status="new"):
self.status = status
class OrderService:
@staticmethod
def cancel(order: AnemicOrder):
if order.status == "shipped":
raise ValueError("Нельзя отменить уже отправленный заказ")
order.status = "cancelled"
# риск: другой сервис может присвоить order.status напрямую, минуя эту проверкуЧто использовать на практике
- Практика. Богатая модель хорошо подходит для core-домена с устойчивыми, явными бизнес-правилами, которые должны соблюдаться всегда и везде, независимо от вызывающего кода (это соответствует идеям domain-driven design об агрегатах, защищающих свои инварианты). Анемичная модель с сервисами часто удобнее на границах системы (API-слой, интеграции, ETL), где объекты по сути являются DTO для передачи данных между слоями/сервисами, а бизнес-правила либо отсутствуют, либо принадлежат другому слою (например, правила валидации на уровне HTTP-запроса, а не домена).
Подводные камни
- Риски. Делают Order богатым объектом, но одновременно позволяют внешнему коду напрямую изменять его поля, минуя методы с проверками (
order.status = "cancelled"напрямую вместоorder.cancel()), из-за чего инкапсуляция инвариантов становится фиктивной. В анемичной модели — размещают схожую логику валидации в нескольких независимых сервисах, которые постепенно расходятся в поведении при изменениях одной из копий, что порождает трудно диагностируемые несоответствия между частями системы. - Компромисс. Богатая модель повышает согласованность данных и локальность бизнес-правил, но увеличивает размер и ответственность объекта и может затруднить переиспользование отдельных операций в контекстах, где нужна лишь часть логики. Анемичная модель облегчает компоновку и переиспользование сервисов, но требует дисциплины, чтобы не дублировать и не расходовать логику валидации по разным местам — выбор зависит от того, насколько критичны инварианты конкретной сущности для бизнеса и сколько независимых путей изменения этой сущности существует в системе.