DDD子实体验证

Rob*_*ert 3 domain-driven-design aggregateroot

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

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

var item = ItemRepository.GetByID(id);

//throws exception if the item is null
order.AddItem(item);
Run Code Online (Sandbox Code Playgroud)

要么

//validate item existence inside aggregate function
order.AddItem(item, IItemRepository repo);
Run Code Online (Sandbox Code Playgroud)

Voi*_*son 5

也不是,真的.

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

order.AddItem(id)
Run Code Online (Sandbox Code Playgroud)

聚合定义的一部分是不同聚合的变化可以彼此独立地发生.换句话说,有没有办法,总能知道什么是发生在那个合计"现在".

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

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

interface InventoryService{
    boolean currentlyInStock(Item id);
}

// ...

order.addItem(id, inventoryService);
Run Code Online (Sandbox Code Playgroud)

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

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

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

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

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

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

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