比方说,我们得到的要求是以下要求。
- 1. 如果订单已经发货了,不要让用户取消订单。
- 2. 如果订单已经被取消,不要让用户运送订单。
这里的竞赛条件是当我们有两个用户在看同一个订单,这个订单既没有取消也没有发货,并且每个人都提交了一个命令:一个是发货,另一个是取消订单。
在这些情况下,代码很简单:只是在执行相关命令前的一个if语句。
时间上的微秒差异不应该对核心业务行为产生影响。这意味着我们实际上在这里得到的是需求中的一个错误。
用户实际上在这里支配着解决方案而不是需求。
与用户对话
让我们问问我们的利益相关者,“为什么我们不应该让用户取消已发货的订单?我的意思是,用户并不想要这些产品”。
而利益相关者会回答说:“好吧,那我们也不想退还用户的钱。或者,至少,不是他们所有的钱。好吧,也许如果他们用原包装退回产品,*那么*我们可以全额退款。”
随着我们的深入研究,“什么时候需要退款?马上,在同一笔交易中?”
利益相关者会解释说,“不,退款不需要立即返回。”
事实证明,我们忽略了退款的概念,以及假设所有事情都需要立即处理和执行。
一旦我们深入研究了这些需求,我们发现实际上有足够的时间让这两笔交易进行下去。我们只需要在交付的长期运行流程中增加一些检查,看看订单是否被取消,然后把这个流程缩短。
那么,一切都是一个长期运行的流程吗?
这实际上是一个公平的问题——长期运行的流程比最初看起来要普遍得多。
我们看到的是,取消现在是一个没有理由失败的命令——就像CQRS告诉我们的那样。执行此命令时,它会发布计费服务订阅的 OrderCancelled 事件。
然后计费开始一个长时间运行的过程(一个Saga,用NServiceBus术语来说),还监听运输过程中的事件,最终决定何时应该退款,以及退款金额。
更深入的业务分析
当我们与业务利益相关者更多地讨论问题时,我们听到大多数订单实际上是在提交后一小时内被取消的。订单在几天后被取消的情况相当罕见。
在这种情况下,我们可以考虑把接受订单的过程本身作为一个长期运行的流程来建模。
当用户下订单时,我们不会立即发布一个表明订单被接受的事件,而是启动一个传奇–这为一小时后的超时打开了一个缺口。如果在这段时间内有取消订单的命令到来,用户会得到全额退款(因为我们没有收取任何费用,因为计费没有得到接受的事件开始),而传奇只是关闭了自己。如果超时发生在一小时后,而传奇没有收到取消命令,那么订单实际上被接受了,事件被发布了。
是的,Saga无处不在,一旦你学会用业务眼光去看,就不会有任何竞赛条件了。
最后说一下
任何时候,当你看到表明存在竞赛条件的需求时,请深入挖掘。
你有可能发现的是一些额外的业务概念以及时间的引入和长期运行的业务流程的建立。这时的实现将从琐碎的if语句转变成更丰富的Saga故事。