在域驱动设计中获取聚合的正确方法

Proper way to get aggregates in Domain Driven Design

DDD 架构中,我有一个 Order 聚合根和一个 Product 聚合。当我想将 Product 添加到特定的 Order 时,我会这样做:

class Order
{
  void AddProduct(Product p)
  {
    this.Products = p;
  }
}

然后,OrderRepository 将内存中的 Order.Products colleciton 保存到数据库中。

我的问题是:如何为特定的 Order 获取特定的 Product?鉴于我们不应该将存储库注入实体,我不确定如何水化内存中的 Order.Products 集合:

class Order
{
  Product GetProduct(int productID)
  {
    return this.Products.Where(x => x.ProductID == productID);
  }
}

或者这是属于 OrderRepository 的东西?

class Order
{
    void AddProduct(Product p)
    {
        this.Products = p;
    }
}

这个模型可能是错误的。

(1)如果Product的状态需要满足Order中的业务不变性,那么Product应该是Order聚合中的实体,而不是单独的聚合它自己的。

另一方面,如果 Order 不需要知道任何关于 Product 的状态来满足它自己的不变量,那么 Products 集合不应该包含产品,而是其他东西(大概Id<Product>

提示:通过更改 Order 来编辑 Product 是否有意义?

(2) 大多数关于 Order 聚合的讨论得出的结论是该顺序持有 OrderItems 的集合(注意:不是聚合),其中每个 OrderItem 持有一个 引用 到产品集合。

购物车是 DDD 讨论中相当常见的示例案例

我知道这个问题有一个公认的答案,但我还是会给出我的意见:)

聚合根 (AR) 不应引用另一个 AR。它只会使用相关 AR 的 Id 或包含相关 AR 的 Id 以及一些可选的额外数据的值对象 (VO)。

Order -> Product 关系的有趣之处在于它是数据库术语中典型的多对多关系。在数据库世界中,人们通常会通过创建一个名为 OrderProduct 的关联(或 link)table 来建模,并为与该关联相关的任何数据添加列,例如数量和价格.然而,不知何故,我们都使用的 table 被称为 OrderItemOrderLine。我们这样做是因为让关联更接近订单是有意义的。

我有一个博客 post 围绕这个仍然有些正确:http://www.ebenroux.co.za/design/2009/09/08/many-to-many-aggregate-roots/

在 DDD 世界中,我们 Order 不会保留对 Product 实例的对象引用列表。相反,我们将创建一个名为 OrderItem 的 VO,它仅包含 ProductId 以及 QuantityPrice 和一个非规范化的 ProductDescription,因为该描述可能会在之后更改订单已经下达,从历史上看,这会改变订购的内容,这将是一个禁忌。

只是一些发人深省的东西:)