DDD子实体验证

3

哪一层应该负责检查数据库中某个实体的存在?假设我有一个订单作为聚合,而这个订单可以包含多个物品。逻辑意味着我只能将现有的物品添加到订单中。

在应用程序服务中,我应该这样写:

var item = ItemRepository.GetByID(id);

//throws exception if the item is null
order.AddItem(item);

或者

//validate item existence inside aggregate function
order.AddItem(item, IItemRepository repo);
1个回答

5

其实两种情况都不是。

实体不会越过聚合边界。如果实体是聚合的一部分,那么聚合将管理其生命周期,或者该项是其他聚合的一部分,在这种情况下,您不共享实体,而是共享一个引用。

order.AddItem(id)

聚合的定义之一是不同聚合的更改可以独立于彼此发生。换句话说,这个聚合无法知道“现在”那个聚合正在发生什么。

换句话说,你无法确保跨聚合边界的事务一致性。

如果您愿意接受数据竞争,正确的答案是使用域服务来查询边界外的状态。

interface InventoryService{
    boolean currentlyInStock(Item id);
}

// ...

order.addItem(id, inventoryService);

一些要点: 使用领域服务而不是传递其他仓库,因为它更好地传达了正在发生的事情。领域服务作为聚合实际需要的合同的描述。此外,通过拒绝传递存储库,您排除了订单聚合尝试写入项目存储库的任何可能性。
(此领域服务的琐碎实现只是将调用转发到存储库,但订单聚合不需要知道这一点)。
在这种情况下,领域服务不应该通过根据库存的可用性选择操作来“帮助”--也许当订单超过一百万美元时,聚合应该抛出异常,但对于低交易量订单/低优先级购买者使用不同规则。弄清楚这一点是订单的工作,领域服务只提供数据。
由于数据竞争,一些误报可能会滑过去;检测和缓解是个好主意。
如果您不愿接受数据竞争(您确定吗?亚马逊经常接受缺货商品的订单...),那么您需要重新考虑模型的设计以及设置聚合边界的位置。
模型的空设计是将所有业务状态捕获到单个聚合中;它自己的状态在内部是一致的,但可能与外部状态不一致。当您开始将模型划分为单独的聚合时,您正在做出相同的断言--聚合需要在内部保持一致,但可能与聚合外部状态不一致。
如果这是不可接受的,则请将母亲的画像转到墙上,并直接在记录簿中实现业务规则(即,在RDBMS中约束)。

这样注入服务并进行必要的检查是完全可以的,没问题吧? - Robert
1
我更喜欢将整个“Item”实例传递给“add”方法。这更符合普遍语言,并且您可以免费或几乎免费地获得项目存在检查,因为我们可以假设没有人会手动创建新的“Item”实例。然后,“Order”将在内部保留id。这种方法也有缺点,但在DDD中,普遍语言应该占主导地位。 - plalx

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接