假设我们有一个类型为 Order 的聚合根实体,它关联客户和订单行。当我考虑订单实体时,更自然地将其概念化为没有 Id 就没有定义。没有 Id 的订单似乎比订单更好地表示为订单请求。
要将订单添加到存储库,我通常会看到人们在没有 Id 的情况下实例化订单,然后让存储库完成对象:
class OrderRepository
{
void Add(Order order)
{
// Insert order into db and populate Id of new order
}
}
我喜欢这种方法的一点是,您正在向 OrderRepository 添加一个 Order 实例。这很有意义。但是,订单实例没有 ID,在存储库的使用者范围内,订单没有 ID 对我来说仍然没有任何意义。我可以将 OrderRequest 定义为 order 的一个实例并将其添加到存储库中,但这感觉就像从一个橙子中获取一个苹果,然后将它添加到一个橙子列表中。
或者,我也看到了这种方法:
class OrderRepository
{
Order AddOrder(Customer customer)
// It might be better to call this CreateOrder
{
// Insert record into db and return a new instance of Order
}
}
我喜欢这种方法的一点是,没有 Id 的订单是未定义的。在创建和返回订单实例之前,存储库可以创建数据库记录并收集所有必需的字段。这里的味道是,您实际上从未将订单实例添加到存储库中。
无论哪种方式都有效,所以我的问题是:我是否必须接受这两种解释中的一种,或者是否有最佳实践来模拟插入?
我发现这个答案很相似,但对于值对象:
how should i add an object into a collection maintained by aggregate root .当谈到值对象时,没有混淆,但我的问题涉及具有从外部源(自动生成的数据库 ID)派生的标识的实体。
最佳答案
我想首先排除第二种方法。这不仅看起来违反直觉,而且还违反了几个好的设计原则,例如 Command-Query Separation和 Principle of Least Surprise .
其余选项取决于域逻辑。如果领域逻辑规定没有 ID 的 Order 没有意义,那么 ID 是 Order 的必需不变量,我们必须对其进行建模:
public class Order
{
private readonly int id;
public Order(int id)
{
// consider a Guard Clause here if you have constraints on the ID
this.id = id;
}
}
请注意,通过标记
id
字段为 readonly
我们使它成为一个不变量。对于给定的 Order 实例,我们无法更改它。这与 Domain-Driven Design 完美契合的 实体图案。您可以通过将 Guard Clause 放入构造函数来进一步强制执行域逻辑,以防止 ID 为负数或零。
到现在为止,您可能想知道这如何与数据库中自动生成的 ID 一起工作。好吧,它没有。
没有好的方法可以确保提供的 ID 尚未被使用。
这让您有两个选择:
在许多情况下,创建新订单是一项在任何情况下都需要特定建模的业务操作,因此我认为进行这种区分没有问题。尽管 Order 和 OrderRequest 在语义上可能非常相似,但它们在类型层次结构中甚至不必相关。
我什至可以说它们不应该相关,因为 OrderRequest 是 值对象 而 Order 是 实体 .
如果采用这种方法,AddOrder 方法必须返回一个 Order 实例(或至少是 ID),否则我们无法知道刚刚创建的订单的 ID。这让我们回到 CQS 违规,这就是为什么我倾向于 更喜欢实体 ID 的指南 .
关于repository - 应如何表示将聚合根添加到存储库?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/2096705/