DDD子实体验证

时间:2016-06-19 19:48:57

标签: domain-driven-design aggregateroot

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

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

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 个答案:

答案 0 :(得分:5)

两者都没有。

实体不会跨越聚合边界。要么实体是聚合的一部分,在这种情况下聚合管理自己的生命周期,或者项目是其他聚合的一部分,在这种情况下,您不共享实体,您共享参考。

order.AddItem(id)

聚合定义的一部分是不同聚合的变化可以彼此独立地发生。换句话说,聚合无法知道 聚合“现在”正在发生什么。

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

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

interface InventoryService{
    boolean currentlyInStock(Item id);
}

// ...

order.addItem(id, inventoryService);

几点: 使用域服务而不是传入其他存储库,因为它可以更好地传达正在发生的事情。域服务用作聚合实际需要的合同的描述。此外,通过拒绝传递存储库,您可以排除订单聚合尝试写入项目存储库的任何可能性。

(这个域服务的简单实现只是将调用转发到存储库,但订单聚合不需要知道这一点。)

在这种情况下,域服务应该通过选择基于库存可用性的操作来“帮助” - 也许聚合应该抛出,也许聚合应该抛出低批量订单/低优先级购买者,但在订单超过一百万美元时使用不同的规则。这是订单的工作,域服务只是提供数据。

鉴于数据竞争,一些误报可能会漏掉;检测和缓解是一个好主意。

如果您不愿意接受数据竞赛(您确定吗?亚马逊一直接受缺货商品的订单......),那么您需要重新考虑您的模型的设计,以及您的位置设置你的聚合边界。

模型的空设计是将所有业务状态捕获到单个聚合中;它自己的状态在内部是一致的,但它可能与外部状态不一致。当您开始将模型划分为单独的聚合时,您将进行相同的断言 - 聚合需要内部一致,但可能与聚合之外的状态不一致。

如果这是不可接受的,那么你将母亲的照片转到墙上,直接在记录簿中实现你的业务规则(即你的RDBMS中的约束)。