DDDesigner

Тактическое моделирование

Агрегат в DDD: согласованность и бизнес-инварианты

Агрегат — группа доменных объектов, рассматриваемая как единая граница согласованности. Все изменения проходят через корень агрегата, который защищает бизнес-инварианты. Это не просто набор таблиц или удобный граф объектов: агрегат является минимальной границей, внутри которой бизнес требует немедленной согласованности.

Что включать в агрегат

Объекты принадлежат одному агрегату, когда бизнес-правило должно оставаться истинным сразу после выполнения команды. Удобство экрана или JOIN в базе не являются достаточной причиной. Делайте агрегаты небольшими и ссылайтесь на другие агрегаты по идентификатору.

  • Одна транзакция изменяет один агрегат
  • Корень — единственная внешняя точка входа
  • Команды выражают намерение
  • Доменные события фиксируют принятые факты

Сначала инварианты, потом структура

Начните с правил: «подтверждённый заказ нельзя изменять» или «кредит не превышает лимит». Объекты и методы должны существовать ради этих правил, а не копировать реляционную схему.

Частая ошибка

Большие графы объектов создают конкуренцию и связанность. Если временную рассогласованность можно устранить событием или процессом, понятия обычно не обязаны находиться в одном агрегате.

Выбирайте границу через инварианты

Начните с правил, которые должны быть истинными сразу после команды. «Заказ нельзя подтвердить без адреса доставки» — инвариант. «На экране заказа показываются данные клиента» — требование к запросу, но не доказательство того, что Customer входит в агрегат Order.

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

Команды, методы и события

Команда выражает намерение: PlaceOrder, AddOrderLine или ConfirmOrder. Корень агрегата принимает её через содержательный метод, проверяет права и инварианты, изменяет состояние и записывает доменные события — факты о принятых изменениях. Событие не должно создаваться для попытки, завершившейся ошибкой валидации.

Решения, зависящие от состояния агрегата, должны находиться внутри него. Обработчик или application service может загрузить агрегат, вызвать поведение, сохранить результат и опубликовать события, но не должен дублировать бизнес-правила в другом месте.

Размер агрегата и ссылки

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

Корень агрегата не обязан содержать все связанные понятия. Объекты-значения выражают ограниченные понятия без собственной идентичности, Domain Service подходит для решения, затрагивающего несколько агрегатов, а сага или политика координирует реакции на события.

Конкурентные изменения и ошибки

Агрегат является ещё и инструментом управления конкуренцией. Оптимистическая проверка версии обнаруживает две команды, изменившие одну и ту же версию. Система может повторить операцию, отклонить её или запросить решение пользователя — в зависимости от бизнес-правила.

При ошибке команды агрегат не должен частично измениться и публиковать событие об успехе. Ошибка в другом контексте — другая ситуация: первоначальное событие остаётся фактом, а для исправления может потребоваться компенсирующее бизнес-действие или шаг саги.

Чек-лист проверки агрегата

Перед утверждением дизайна проверьте, что граница оправдана инвариантами, а не JOIN-ами базы или композицией экрана.

  • Каждое изменение проходит через корень?
  • Инварианты явно записаны?
  • Всё включённое состояние нужно одной транзакции?
  • Другие агрегаты представлены идентификаторами?
  • События сформулированы как факты в прошедшем времени?
  • Понятно поведение при конкурентных командах?

DDDesigner

Пример: агрегат Order и выполнение заказа

Агрегат Order владеет строками, решениями о цене, ссылкой на клиента и жизненным циклом заказа. PlaceOrder проверяет наличие хотя бы одной строки и обязательных данных. ConfirmOrder проверяет правила подтверждения и создаёт OrderConfirmed. Inventory и Payments не вкладываются в Order: они реагируют на события в собственных контекстах. Сага выполнения заказа координирует резервирование, оплату и доставку, а агрегат Order остаётся ответственным за инварианты самого заказа.

Начать бесплатно