Domain driven design DDD:在聚合根目录中保留指向实体的链接,仅用于报告

Domain driven design DDD:在聚合根目录中保留指向实体的链接,仅用于报告,domain-driven-design,entity-relationship,aggregate,ddd-repositories,aggregateroot,Domain Driven Design,Entity Relationship,Aggregate,Ddd Repositories,Aggregateroot,我正在使用DDD重构一个项目,但我担心不要让太多的实体成为自己的聚合根 我有一个商店,它有一个产品选项s列表和一个产品s列表。一个ProductOption可由多个Products使用。这些实体似乎非常适合存储聚合 然后我有一个订单,它暂时使用产品来构建其订单行s: class Order { // ... public function addOrderLine(Product $product, $quantity) { $orderLine = new Or

我正在使用DDD重构一个项目,但我担心不要让太多的实体成为自己的聚合根

我有一个
商店
,它有一个
产品选项
s列表和一个
产品
s列表。一个
ProductOption
可由多个
Product
s使用。这些实体似乎非常适合
存储
聚合

然后我有一个
订单
,它暂时使用
产品
来构建其
订单行
s:

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规则是受尊重的。但我想添加一个要求,这可能会打破聚合规则:店主有时需要检查包含特定产品的订单的统计信息

这意味着,基本上,我们需要在订单行中保留对产品的引用,但实体中的任何方法都不会使用该引用。在查询数据库时,我们仅将此信息用于报告目的;因此,由于该内部引用,不可能“破坏”存储聚合中的任何内容:

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;
    }
}
这个简单的需求是否要求产品成为聚合根?这还将级联到ProductOption,使其成为聚合根,因为Product有对它的引用,从而导致两个聚合在存储之外没有意义,并且不需要任何存储库;我觉得很奇怪


欢迎评论

在这种情况下,您需要与聚合根无关的报告信息

因此,最适合它的地方应该是一个服务(如果它与业务相关,可以是域服务,或者更好的是像查询服务这样的应用程序服务,它查询所需的数据并将其作为DTO返回,可为演示或消费者定制)

我建议您创建一个statistics services,它使用只读存储库(或更好的查找器)查询所需数据,返回DTO,而不是使用查询模型破坏域


检查

即使是“仅用于报告”,仍然有业务/领域的含义。我认为您的设计很好。虽然我不会通过存储
订单行->产品
参考来处理新需求。我会在产品名称和价格方面做一些与您已经做的类似的事情。您只需要存储订单行中某种类型的产品标识符(?)。此标识符/SKU可在以后的查询中使用。SKU可以是存储和产品自然密钥的组合:

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

class OrderLine {
    private Money _price;
    private int _quantity;
    private String _productName;
    private Sku _productSku;
}
这样,您就不会违反任何聚合规则,并且可以安全地删除产品和存储,而不会影响现有或存档的订单。您仍然可以拥有“来自StoreY的ProductX订单”


更新:关于您对外键的担忧。在我看来,外键只是一种在数据库级别强制执行长期存在的域关系的机制。由于您没有域关系,您也不需要强制执行机制。

谢谢,这是否意味着可以在订单行中显式存储产品:
$this->product=$product;
?谢谢,这听起来是一个合理的方法。我们没有SKU,只有一个自动生成的
productId
。我对这种方法唯一感到遗憾的是失去了在数据库中使用外键的好处(如果我这样做了,那么删除产品就会失败)。关于这一点,你有什么建议吗?