Ответ
В DDD агрегат — это кластер связанных объектов, рассматриваемых как единое целое с корнем (Aggregate Root), который контролирует доступ и гарантирует инварианты. Разделение одного агрегата на несколько — это серьёзное архитектурное решение, которое влечёт за собой проблемы:
-
Нарушение транзакционной целостности: Изменения, которые раньше были атомарными в рамках одного агрегата, теперь требуют обновления нескольких агрегатов. Это либо приводит к необходимости использования распределённых транзакций (сложных и медленных), либо к согласованности в конечном счёте (eventual consistency) через доменные события.
-
Усложнение поддержки инвариантов: Бизнес-правило (инвариант), которое раньше проверялось внутри одного агрегата, теперь может затрагивать несколько. Его обеспечение требует координации, часто через обработку доменных событий или использование паттерна Saga.
- Было: В агрегате
Orderможно было гарантировать, что сумма всехOrderItemне превышает кредитный лимит пользователя. - Стало: После выделения
OrderItemв отдельный агрегат это правило нужно проверять в сервисе домена, который координирует оба агрегата.
- Было: В агрегате
-
Ухудшение производительности при загрузке: Чтобы выполнить операцию, теперь может потребоваться загрузить несколько агрегатов вместо одного, увеличивая число запросов к БД.
-
Возрастание сложности: Архитектура становится сложнее из-за появления новых сущностей, событий и процессов согласования. Это увеличивает порог входа для новых разработчиков.
Пример проблемы:
// Монолитный агрегат Заказ (просто, но может быть большим)
class Order {
private UUID id;
private List<OrderLine> lines; // Value Objects внутри агрегата
private Money total;
// Инвариант: total = sum(lines.price) поддерживается внутри
public void addLine(Product product, int quantity) {
// Проверка лимитов, пересчёт total — всё здесь
}
}
// После разделения (гибче, но сложнее)
class Order { // Агрегат 1
private UUID id;
private Money total;
}
class OrderLine { // Агрегат 2
private UUID orderId;
private ProductId productId;
private Money price;
}
// Теперь для добавления товара нужно:
// 1. Загрузить Order.
// 2. Создать OrderLine (отдельная транзакция).
// 3. Отправить событие OrderLineAdded.
// 4. Обработать событие и пересчитать total в Order (ещё одна транзакция).
Решение о разделении должно быть взвешенным и оправдываться требованиями масштабируемости или независимой жизненной цикличности частей домена.