Ed *_*d I 5 entity domain-driven-design repository aggregateroot
假设我们有一个Order类型的聚合根实体,它关联客户和订单行.当我考虑一个订单实体时,将其概念化为没有Id的定义更为自然.没有Id的订单似乎更好地表示为订单请求而不是订单.
要向存储库添加订单,我通常会看到人们在没有Id的情况下实例化订单,然后让存储库完成对象:
class OrderRepository
{
void Add(Order order)
{
// Insert order into db and populate Id of new order
}
}
Run Code Online (Sandbox Code Playgroud)
我喜欢这种方法的是你将Order实例添加到OrderRepository.这很有道理.但是,订单实例没有Id,并且在存储库的使用者的范围内,对我来说,订单没有Id也没有意义.我可以将OrderRequest定义为订单的实例并将其添加到存储库,但这感觉就像从橙色中导出苹果然后将其添加到橙色列表中.
或者,我也看到过这种方法:
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
}
}
Run Code Online (Sandbox Code Playgroud)
我喜欢这种方法的是没有Id的订单是未定义的.存储库可以创建数据库记录并在创建和返回订单实例之前收集所有必需的字段.这里有什么味道,你实际上从未向存储库添加订单实例.
无论哪种方式都有效,所以我的问题是:我是否必须接受这两种解释中的一种,或者是否有最佳实践来模拟插入?
我发现这个答案是类似的,但对于值对象: 我应该如何将对象添加到由聚合根维护的集合中.当涉及到一个价值对象时,没有混淆,但我的问题涉及一个具有来自外部源(自动生成的数据库ID)的标识的实体.
我想首先排除第二种方法.它不仅看起来违反直觉,而且还违反了一些好的设计原则,例如命令查询分离和最小惊喜原则.
其余选项取决于域逻辑.如果域逻辑规定没有ID的订单没有意义,则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;
}
}
Run Code Online (Sandbox Code Playgroud)
请注意,通过标记id字段,readonly我们已将其设为不变量.我们无法为给定的Order实例更改它.这非常适合Domain-Driven Design的实体模式.
您可以通过将Guard子句放入构造函数来进一步强制执行域逻辑,以防止ID为负或零.
到目前为止,您可能想知道这可能如何与数据库中自动生成的ID一起使用.嗯,事实并非如此.
没有好办法确保提供的ID尚未使用.
这有两个选择:
在许多情况下,创建新订单是一种在任何情况下都需要特定建模的业务操作,因此我认为没有问题可以做出这种区分.尽管Order和OrderRequest在语义上可能非常相似,但它们甚至不必在类型层次结构中相关.
我甚至可能会说它们不应该相关,因为OrderRequest是一个Value Object而Order是一个实体.
如果采用这种方法,AddOrder方法必须返回Order实例(或至少ID),否则我们无法知道刚创建的订单的ID.这导致我们回到CQS违规,这就是为什么我倾向于选择Guids for Entity ID.