保持聚合之间的一致性

时间:2017-04-04 19:35:20

标签: domain-driven-design consistency aggregateroot

我想知道如何解决聚合之间的事务一致性问题。我的第一印象是,无论何时需要聚合之间的事务一致性,您都错误地设计了聚合。但是,我仍然想问这个问题,以确保我没有遗漏任何东西。

想象一下,你卖牛奶。你有多头奶牛,每头奶牛每天产一定量的牛奶。您需要一项能够获得库存牛奶量的服务。除此之外,您还应该能够订购牛奶。根据此信息,您可以创建三个聚合,CowStockOrder。每当订购一定数量的牛奶时,其中一个业务规则是检查该金额是否有库存,如果没有,请立即告知用户。当两个用户同时发出请求并订购总量为150升的牛奶时,如何才能实现这一目标,而只有130升可用?我的第一个想法是你可以通过乐观/悲观锁定实现这一点,但在这种情况下,一个聚合依赖于另一个。是否有某种方法可以解决这个特定的问题,或者这只是糟糕的聚合设计?

2 个答案:

答案 0 :(得分:5)

  

我的第一印象是,无论何时需要聚合之间的事务一致性,您都错误地设计了聚合。

这是完全正确的;发现聚合边界的方式首先是通过识别需要保持一致的值,然后选择具有任何两个值需要相互一致的值在同一边界内的属性的边界。

注意:我们并不总能做到这一点;也许这些要求是错误的,也许我们的模型不够充分,也许业务发生了变化。部分挑战是确保用更好的模型替换当前模型总是很容易。

  

根据此信息,您可以创建三个聚合,牛,股票和订单。

注意:Cow是一个糟糕的聚合 - 假设我们在谈论世界上吃草牛的真实情况。它是一个实体,是的,但它超出了模型的影响。如果模型说奶牛是空的,奶牛说它里面装满了牛奶,奶牛就是对的。

同样,如果奶牛是真的,那么你的大部分股票都是真实的。如果模型说有七个装满牛奶的罐子,而农民就有六个,那么六个就是正确答案。

聚合是信息资源

  

每当订购一定数量的牛奶时,其中一项业务规则是检查该金额是否有库存,如果没有,请立即告知用户。当两个用户同时执行请求并订购总量为150升的牛奶时,如何才能实现这一目标,而只有130升可用?

关于“马上”的重要事项;你在这里,用户(买家?)在那里。通信中存在一定的延迟,这意味着您发送给买方的信息在到达时已经过时。 (从技术上讲,当你发送它时,它已经过时了。)

第二次履行订单需要了解订单和可用库存。因此,您可以将其建模为执行所有操作的单个聚合,或者您可以将其建模为与某个履行聚合异步通信的订单聚合;例如,可能是Stock真正做的是将可用牛奶的通知与挂单相匹配。

在您的并发处理示例中,这看起来像两个命令,用于保留130升牛奶同时运行与150升牛奶可用的库存集合。使用乐观并发这两个命令都会发现有足够的牛奶来满足订单,并会尝试更新记录簿。然而,该更新是序列化的 - 想想事务,或比较和交换 - 因此其中一个命令成功,另一个获得并发修改异常。因此,第二个命令再次尝试,在新状态下重新加载库存。这一次,它发现可用库存不足,并采取相应行动(将订单移至等待名单,告知买方预计的履行日期等)。

请注意,如果预订牛奶的命令与更多牛奶可用的公告并行运行,您也会获得并发修改例外。

答案 1 :(得分:3)

  

我的第一印象是,无论何时需要聚合之间的事务一致性,您都错误地设计了聚合。

我会在这里向另一个方向说:不一定。您总是要求在某个时间点聚合之间保持一致。唯一的问题是何时。你可能会发现在某些情况下你遇到了一些政治问题。我必须在一大堆聚合之间实现100%的即时一致性,因为我被那些持有钱包的人指示这样做。在理想的世界里,我们可以自由选择。但在我的现实世界中,你可以打赌我们会失败。无论如何,除了歌词之外,肯定 可能会远离一致性。

在事务中更改多个聚合并不是世界末日,但如果你可以避免你当然应该。它确实带来了一些可以预期的轻微开销。

在您的示例中,您可以检查库存水平,但如上所述,并发读取会导致误导性信息。但是,您可以下订单,在您开始流程之前,您可以使用将成为您的流程经理的ID和您的整体“相关ID”来“保留”数量。如果你不能保留股票,你可以放弃订单。但是,对于包含多个商品的特定订单,您必须保留多个商品。

有很多方法(航空公司预订也可以作为例子)。你可能想要接受订单,看看你能做些什么。订单可能有不同的周转时间和更多牛奶或其他项目,可能同时可用。您可以通知客户他们的订单无法填写并已被取消或通知他们点击链接调整订单或者接受替代订单。

只是一些想法:)