Почему при проектировании предметной области (DDD) может получаться слишком большой агрегат?

«Почему при проектировании предметной области (DDD) может получаться слишком большой агрегат?» — вопрос из категории Архитектура, который задают на 24% собеседований PHP Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

Большой агрегат (Aggregate Root с множеством сущностей и value objects) часто возникает из-за нарушения принципов проектирования границ контекста и инвариантов.

Основные причины:

  1. Неверно определенные инварианты консистентности. Агрегат должен защищать инварианты только в своих границах. Если вы пытаетесь обеспечить консистентность между сущностями, которые могут изменяться независимо, это признак того, что они должны быть разными агрегатами.
  2. Страх ссылок между агрегатами. Разработчики иногда включают всё в один агрегат, чтобы избежать использования ID для ссылок и обеспечения eventual consistency.
  3. Агрегат как отражение таблицы БД. Это антипаттерн. Агрегат — это граница транзакции, а не схема данных.
  4. Нарушение принципа единственной ответственности (SRP). Агрегат начинает отвечать за слишком много бизнес-процессов.

Пример проблемы и решения:

Проблема: Агрегат Order управляет и заказом, и инвентарём.

// Слишком большой агрегат
class Order {
    private List<OrderLine> lines;
    private List<InventoryItem> inventoryItems; // Это должен быть отдельный агрегат!
    // ... методы для резервирования товара, изменения его количества и т.д.
}

Решение: Выделить InventoryItem в отдельный агрегат. Согласованность между Order и Inventory поддерживается через доменные события (Domain Events) или saga.

class Order {
    private UUID id;
    private List<OrderLine> lines;

    public OrderPlacedEvent place() {
        // ... логика размещения заказа
        return new OrderPlacedEvent(this.id, this.lines); // Событие для резервирования товара
    }
}

class InventoryItem {
    private UUID productId;
    private int quantity;

    public void reserveForOrder(UUID orderId, int amount) { ... }
}

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