DDD: сохранить ссылку на объект внутри совокупного корня только для отчетности

Я занимаюсь рефакторингом проекта с использованием DDD, но меня беспокоит, что слишком много сущностей не станут их собственными агрегатными корнями.

У меня есть Store, в котором есть список ProductOption и список Product. ProductOption может использоваться несколькими Product. Эти сущности, кажется, очень хорошо вписываются в агрегат Store.

Затем у меня есть Order, который временно использует Product для создания своих OrderLine:

class Order {
    // ...
    public function addOrderLine(Product $product, $quantity) {
        $orderLine = new OrderLine($product, $quantity);
        $this->orderLines->add($orderLine);
    }
}

class OrderLine {
    // ...
    public function __construct(Product $product, $quantity) {
        $this->productName = $product->getName();
        $this->basePrice = $product->getPrice();
        $this->quantity = $quantity;
    }
}

Похоже, что на данный момент правила DDD соблюдаются. Но я хотел бы добавить требование, которое может нарушить правила агрегирования: владельцу Магазина иногда нужно проверять статистику о Заказах, которые включали конкретный Товар.

Это означает, что в основном нам нужно сохранить ссылку на Продукт в OrderLine, но это никогда не будет использоваться каким-либо методом внутри сущности. Мы будем использовать эту информацию только в целях отчетности при запросе базы данных; таким образом, было бы невозможно "сломать" что-либо внутри агрегата Store из-за этой внутренней ссылки:

class OrderLine {
    // ...
    public function __construct(Product $product, $quantity) {
        $this->productName = $product->getName();
        $this->basePrice = $product->getPrice();
        $this->quantity = $quantity;

        // store this information, but don't use it in any method
        $this->product = $product;
    }
}

Означает ли это простое требование, что Product становится совокупным корнем? Это также приведет к тому, что ProductOption станет агрегированным корнем, поскольку у Product есть ссылка на него, что приведет к двум агрегатам, которые не имеют значения вне Store и не будут нуждаться в каком-либо репозитории; мне кажется странным.

Любые комментарии приветствуются!




Ответы (2)


Несмотря на то, что он предназначен только для отчетности, в нем все же есть значение бизнеса / домена. Я считаю, что ваш дизайн хорош. Хотя я бы не справился с новым требованием, сохраняя OrderLine -> Product ссылку. Я бы сделал что-то похожее на то, что вы уже делаете, с названием продукта и ценой. Вам просто нужно сохранить какой-то идентификатор продукта (SKU?) В строке заказа. . Этот идентификатор / SKU можно позже использовать в запросе. Артикул может быть комбинацией естественных ключей Store и Product:

class Sku {
    private String _storeNumber;
    private String _someProductIdUniqueWithinStore;
}

class OrderLine {
    private Money _price;
    private int _quantity;
    private String _productName;
    private Sku _productSku;
}

Таким образом, вы не нарушаете какие-либо сводные правила, а продукт и магазины можно безопасно удалить, не затрагивая существующие или заархивированные заказы. И вы по-прежнему можете получать «Заказы с помощью ProductX из StoreY».

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

person Dmitry    schedule 16.09.2011
comment
Спасибо, это звучит разумно. У нас нет артикула, только автоматически сгенерированный productId. Единственное, о чем я сожалею об этом подходе, - это потеря преимущества использования внешнего ключа в БД (если бы я это сделал, то удаление продукта не удалось бы). Что вы можете посоветовать по этому поводу? - person BenMorel; 16.09.2011

В этом случае вам понадобится информация для отчетности, которая не имеет ничего общего с совокупным корнем.

Таким образом, наиболее подходящим местом для этого будет служба (может быть служба домена, если она связана с бизнесом, или, что лучше, служба приложений, например служба запросов, которая запрашивает необходимые данные и возвращает их в виде DTO, настраиваемых для представления или потребителя.

Я предлагаю вам создать статистические службы, которые запрашивают необходимые данные, используя репозитории только для чтения (или предпочтительные Finders), которые возвращают DTO вместо того, чтобы портить домен моделями запросов.

Проверьте это

person Mohamed Abed    schedule 15.09.2011
comment
Спасибо, значит ли это, что можно явно хранить Продукт в OrderLine: $this->product = $product;? - person BenMorel; 15.09.2011