DDD - 非规范化实体作为具有ID的值对象?

时间:2018-02-27 12:03:28

标签: model domain-driven-design denormalization value-objects

假设我们在仓库环境中有订单。我们有客户即可获得该订单。

虽然在数据库Client中有一个ID和一堆参数,但我将其建模为我的订单中的值对象:

class Order extends AggregateRoot {
    private client: Client;
    private shipping: Shipping;
    private items: Array<OrderItem>;
}

class Client extends ValueObject {
    public readonly name: string;
    public readonly contactPhone: string;
    public readonly contactEmail: string;
}

我将其建模为价值对象的原因很简单 - 我不认为仓库关心客户是谁。这些数据主要用于预订快递员,在这种情况下,客户无法真正更改其姓名或联系方式 - 这需要调整快递预订(此时,可能只是将其视为不同的客户)共)。

现在我认为了解客户端ID(对于某些分析或可追溯性)实际上是很好的。易:

class Client extends ValueObject {
    public readonly name: string;
    public readonly contactPhone: string;
    public readonly contactEmail: string;
    public readonly clientId: DomainID; // or ClientID, not essential
}

然而,这是令人困惑的一点:现在这看起来像一个实体(毕竟,我基本上将来自不同上下文的实体非正规化为值对象)。 &#34;身份&#34;这里没有任何意义,因为在检查clientId平等时,我不会关心{/ 1} {/ 1}。

换句话说:出于所有实际目的,仓库关心客户身份。此外,仓库无法更改任何客户详细信息。但是,存在隐含的理解,即我们将运送到同一客户端 - 具有相同身份的客户端。

我是否将Client建模为值对象?这是一个常见的陷阱,而我只是采用&#34;价值对象而不是实体&#34;统治到绝对水平?

2 个答案:

答案 0 :(得分:3)

通常的答案是您通过ID引用客户端,而不是缓存其属性

class Order extends AggregateRoot {
    private clientId: DomainID; // or ClientID, not essential
    private shipping: Shipping;
    private items: Array<OrderItem>;
}

如果您需要它们来维护订单本身的完整性,您只需提取其他客户端属性的缓存副本。

订单中的clientId为您提供了在需要时获取客户端数据副本所需的钩子。通常通过拥有了解如何从客户端ID中查找所需数据副本的域服务来实现挂钩。

答案 1 :(得分:0)

另一种方法是,您还可以在“订单AR”中保留“客户”字段的副本。

您可能会问为什么?

因为订单(但是业务需求可能有所不同)是在单个时间点发生的。如果在给定的时间点CustomerId: 1发生订单,则该客户在这个时间点有name: John DoecontactPhone: 555-abc-xyz。这才是真正重要的事情(再次:业务需求可能会有所不同,但请与您的领域专家交谈)。

如果客户更改了他/她的namecontactPhone,则您可以(或可以不更改,具体取决于用例)针对客户的 PENDING 订单进行更新,但不要更改 COMPLETED 订单(因为更新几周或几年前发生的订单的电话号码是没有意义的。)