【问题标题】:DDD: keep a link to an entity inside an aggregate root, for reporting onlyDDD:在聚合根中保留指向实体的链接,仅用于报告
【发布时间】:2023-03-03 13:15:01
【问题描述】:

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

我有一个Store,它有一个ProductOptions 的列表和一个Products 的列表。一个ProductOption 可以被多个Products 使用。这些实体似乎非常适合 Store 聚合。

然后我有一个Order,它暂时使用Product 来构建它的OrderLines:

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 中保留对 Product 的引用,但这永远不会被实体内的任何方法使用。在查询数据库时,我们只会将此信息用于报告目的;因此,由于此内部引用,不可能“破坏” 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 之外没有任何意义,并且不需要任何存储库;我觉得很奇怪。

欢迎评论!

【问题讨论】:

    标签: domain-driven-design entity-relationship aggregate ddd-repositories aggregateroot


    【解决方案1】:

    尽管它是用于“仅报告”,但仍然存在业务/领域的含义。我觉得你的设计很好。虽然我不会通过存储 OrderLine -> Product 引用来处理新要求。我会做一些类似于你已经对产品名称和价格所做的事情。您只需要在订单行中存储某种产品标识符 (SKU?)。此标识符/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;
    }
    

    这样您就不会违反任何汇总规则,并且可以安全地删除产品和商店,而不会影响现有或存档的订单。而且您仍然可以收到“来自 StoreY 的 ProductX 订单”。

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

    【讨论】:

    • 谢谢,这听起来很合理。我们没有 SKU,只有自动生成的 productId。我对这种方法唯一感到遗憾的是失去了在数据库中使用外键的好处(如果我这样做了,那么删除产品就会失败)。您对这一点有什么建议吗?
    【解决方案2】:

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

    因此,最适合它的地方是服务(如果它与业务相关,则可以是域服务,或者更好地与应用服务相关,例如查询所需数据的查询服务,并将它们作为可定制的 DTO 返回以供演示或消费者使用。

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

    查看this

    【讨论】:

    • 谢谢,这是否意味着可以将产品显式存储在 OrderLine 中:$this->product = $product;
    猜你喜欢
    • 1970-01-01
    • 2019-06-29
    • 2016-11-05
    • 1970-01-01
    • 2020-06-11
    • 1970-01-01
    • 2022-12-16
    • 1970-01-01
    相关资源
    最近更新 更多