哪个层应该负责检查数据库中某个实体的存在? 假设我有一个订单作为聚合,该订单可以包含多个项目。逻辑意味着我只能添加现有的商品。
我应该在应用程序服务中这样写吗:
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);
答案 0 :(得分:5)
两者都没有。
实体不会跨越聚合边界。要么实体是聚合的一部分,在这种情况下聚合管理自己的生命周期,或者项目是其他聚合的一部分,在这种情况下,您不共享实体,您共享参考。
order.AddItem(id)
聚合定义的一部分是不同聚合的变化可以彼此独立地发生。换句话说,此聚合无法知道 聚合“现在”正在发生什么。
换句话说,您无法确保跨聚合边界的事务一致性。
如果您愿意接受数据竞争,正确答案是使用域服务来查询边界外的状态。
interface InventoryService{
boolean currentlyInStock(Item id);
}
// ...
order.addItem(id, inventoryService);
几点: 使用域服务而不是传入其他存储库,因为它可以更好地传达正在发生的事情。域服务用作聚合实际需要的合同的描述。此外,通过拒绝传递存储库,您可以排除订单聚合尝试写入项目存储库的任何可能性。
(这个域服务的简单实现只是将调用转发到存储库,但订单聚合不需要知道这一点。)
在这种情况下,域服务应该不通过选择基于库存可用性的操作来“帮助” - 也许聚合应该抛出,也许聚合应该抛出低批量订单/低优先级购买者,但在订单超过一百万美元时使用不同的规则。这是订单的工作,域服务只是提供数据。
鉴于数据竞争,一些误报可能会漏掉;检测和缓解是一个好主意。
如果您不愿意接受数据竞赛(您确定吗?亚马逊一直接受缺货商品的订单......),那么您需要重新考虑您的模型的设计,以及您的位置设置你的聚合边界。
模型的空设计是将所有业务状态捕获到单个聚合中;它自己的状态在内部是一致的,但它可能与外部状态不一致。当您开始将模型划分为单独的聚合时,您将进行相同的断言 - 聚合需要内部一致,但可能与聚合之外的状态不一致。
如果这是不可接受的,那么你将母亲的照片转到墙上,直接在记录簿中实现你的业务规则(即你的RDBMS中的约束)。