CQRS / 事件溯源 / 事件总线 / 时序

Posted

技术标签:

【中文标题】CQRS / 事件溯源 / 事件总线 / 时序【英文标题】:CQRS / Event Sourcing / Event bus / Timing 【发布时间】:2016-09-05 16:09:31 【问题描述】:

我的 CQRS/ES 设计中有一个计时案例。为了便于讨论,让我们基于微软的 关于这个主题的示例,会议管理 (https://msdn.microsoft.com/en-us/library/jj554200.aspx)。

两个上下文:会议管理和订单管理。在会议管理中,您可以创建会议并更改其属性(例如:最大席位)。会议管理通过事件总线与订单管理进行通信。通过这种方式,订单管理知道何时创建新会议,并在其上下文中实例化跟踪对象(座位可用性)。诸如“最大座位可用性从 100 减少到 80”之类的事件也通过事件总线从 conf mgmt 传递到 order mgmt。

假设在第 1 分钟创建了会议(最多 20 个座位)。 在第 4 分钟,事件到达订单管理上下文,因此创建了座位可用性。 在第 7 分钟,用户下了订单(通过订单管理),购买了全部 20 个席位。 (这也应该从 - 订单管理到 conf 管理进行沟通,以便 conf 管理员可以做出正确的决定)。 在第 8 分钟,会议管理员更改了席位可用性(在会议管理上下文中),将其减少到 15 个。 订单管理的通知(关于已售出所有 20 个座位的事实)在第 9 分钟到达。 现在该怎么办? (在会议上?当您已经收到 20 个席位的付款时,您不应该将席位减少到 15 个;但是在第 8 分钟管理员不知道这件事)。

这是第一种情况。

现在是第二种情况:

假设在第 1 分钟创建了会议(最多 20 个座位)。 在第 4 分钟,事件到达订单管理上下文,因此创建了座位可用性。 在第 6 分钟,会议管理员更改了席位可用性(在会议管理上下文中),将其减少到 15 个。 在第 7 分钟,用户下了订单(通过订单管理),购买了全部 20 个席位。他不知道可用性已减少到 15,因为此时活动尚未到来。 在第 10 分钟,来自 conf mgmt 的通知终于到达。 现在该怎么办?

这些问题源于技术问题(事件传输延迟)。有没有纯粹的技术方法来避免它? 如果没有,克服它的商业方式/设计方式是什么?

补充说明:我知道这是与 CQRS/ES 架构相关的最终一致性问题。在单一上下文中,这可能更容易解决,因为您可以为此设置命令(例如:撤消)。但是,在有界上下文之间你不应该传递命令,你只与事件通信,我认为事件不是这个的正确抽象(因为它代表了发生的事情)。还是我错过了什么?

补充说明:也许这篇文章可以提供更多上下文和提示解决方案,但对于这个问题中的这个特定情况,我不这么认为(这不是关于无序消息)。 http://blog.jonathanoliver.com/cqrs-sagas-with-event-sourcing-part-i-of-ii/

补充说明:或者也许......,在订单管理上下文中,我们可以简单地排队购买座位的命令,直到我们知道我们从会议管理上下文中收到了最新/最后一个事件。我的意思是,事件带有时间戳,对吗?因此,我们可以将我们从 conf mgmt 上下文中收到的最后一个事件的时间戳与座位购买命令的时间戳进行比较。如果最后一个事件的 ts 小于命令的 ts,我们简单地推迟命令的执行,直到我们收到一个 ts 大于命令的 ts 的事件。这种事情将是流程经理(saga)的责任。

这行得通吗?这是正确的方法吗?

添加注释:相关线程> Implementing a Saga/Process Manager in a CQRS http application 。我认为我们在流程管理器方面走在正确的轨道上。正如回答者所说:“您根本不会立即回答“订单确认”。看看亚马逊和其他购物网站是如何做到的:提交订单后,您只会收到“订单接受确认”(例如,HTTP Code 202 Accepted )。”。

由您的流程管理器的逻辑来决定何时实际执行命令(基于特定条件、可能是内部状态,或者在这种情况下是从其他上下文接收到的最新事件)。

有什么意见吗?

谢谢, 拉卡

【问题讨论】:

在你描述的场景中,业务说做什么?询问领域专家在这种情况下应该发生什么 【参考方案1】:

三种可能

首先,您接受最终的一致性。正如评论中所指出的,在许多最终一致性场景中,事实证明,如果及时意识到该问题,企业将有办法缓解问题 - 因此您需要一些东西来观察从模型中发出的事件您在预留的座位数和售出的座位数之间存在差异,并且可能是基于任务的 UI 允许人类做正确的事情 [tm]。

其次,与业务部门核实,看看您是否真正正确地建模了真实的业务流程。例如,您可能会错过购买座位的几个阶段;订单管理可能需要在确认订单前预订座位。

(也就是说,说客户想要 20 个席位的事件可能与说我们实际上将它们卖给她的事件是分开的,这两者都不同于会议管理已保留席位的事件为该客户)。

请注意,这实际上并不能让您免于业务问题 - 在想要购买 20 个席位的客户和想要减少观众人数的会议组织者之间仍然存在竞争条件。你得到的是一个稍微清晰的失败模式(如果客户赢得席位的比赛,那么会议管理可以拒绝减少计数。同样,如果组织者赢得比赛,保留席位的尝试将被拒绝)。

这假定会议管理应保持保留席位的数量小于分配的数量的不变量。在您的业务中,这实际上可能并非如此。此外,能够跟踪客户尝试预订多于可用座位的次数可能对企业很有用。

第三,可能是你的服务边界画错了地方。 Udi Dahan wrote

任何一条数据或规则都必须由一个服务拥有。

如果会议管理中的席位和订单管理中的席位是同一个东西,那么它们可能属于一起(相同的服务边界)。

我的建议是,基于自动化应该能够在需要时避开领域专家的原则,我的建议是,您应该主要考虑第一种方法 - 您能否提出问题和将控制权交给业务专家。如果你做对了,那么你很可能会有一个成功的项目。

【讨论】:

【参考方案2】:

正如我的评论和此处的其他答案中提到的,您应该首先与业务部门核实在这种情况下会发生什么。如果你把问题交给企业,事情通常会简单得多。

处理这些事情的通常方法是采取某种补偿措施。例如,如果您要在标准电子商务网站上下订单,但在处理时商品缺货,您通常会收到补偿措施 - 一封电子邮件说它缺货,也许是在替代项目等...

在您的示例中,如果有人订购了 20 个座位,但随后减少到 15 个,您可以:

完全取消订单,全额退款 将订单减少到 15 个并为剩余的座位提供退款 保持原样,接受超额预订 - 也许总有人取消或不出现

他们就在我的脑海中......

重要的是问业务。他们经常会让你大吃一惊!

如果这个答案返回它永远不会发生,那么另一个答案对此有一些明智的建议。

【讨论】:

【参考方案3】:

让我们开始吧,我认为,您的设计中最大的问题:您的聚合需要很长时间才能更新。

设计产品的好方法是这样的: GUI -> API -> Command -> CommandHandler[告诉聚合可以更新的东西] --> 事件[首先去聚合,然后去其他订阅者]。您还需要确保在处理新命令之前应用事件。因此,您的 CommandHandler 应该在开始新命令之前完成对命令的处理。现在,考虑到您希望在任何给定时间单位内处理多少命令,您必须在此后对其进行建模。 但是如果你这样做,你可以确保聚合可以做出适当的决定,如果命令是有效的。

为什么我认为这是一个大问题?因为如果您有一个想要预订所有座位的指挥部,您需要确保有那么多座位可用。您确实想要一个落后的事件,从而减少了可用席位的数量。聚合必须始终具有真实信息。否则,您将创建无效事件,这是一个很大的禁忌。

所有这些都提出了“业务逻辑应该在哪里?”的问题。嗯,这可能有点棘手。聚合是唯一(或应该是唯一)知道它处于哪种状态的。但是 CommandHandler 还应该检查 Command 是否有效。因此,您的逻辑可以在聚合和 commandHandler 之间拆分。进行拆分的一种简单方法是,CommandHandler 应该只检查它是否是有效命令,而 Aggregate 应该检查“我真的可以这样做吗?”。

所以,如果我们想象上述分裂并且我们有您的场景 1。

    CreateConferenceCommand("MyConference", 20 个席位) ConferenceCommandHandler(创建会议命令命令) 检查命令是否包含所有需要的信息 告诉聚合一个命令到达并要求更新信息。 聚合确定更改。 在本例中,已创建聚合并拥有 20 个席位。 ConferenceUpdatedEvent 创建并发布。 OrderCommand(“我的会议”,20 个席位) 这实际上意味着什么(如果它已得到确认以及所有这些)取决于您。 ConferenceCommandHandler(OrderCommand 命令) 检查命令是否包含所有需要的信息 告诉聚合一个命令到达并要求更新信息。 聚合确定更改。 它有 20 个座位,所以可以更改。 ConferenceUpdatedEvent 创建并发布 “MyConference”将可用席位减少到 20 个。 ChangeSeatsCommand("MyConference", 15 个席位) ConferenceCommandHandler(ChangeSeatsCommand 命令) 检查命令是否包含所有需要的信息。 告诉聚合一个命令到达并要求更新信息。 Aggreget 确定更改。 由于所有座位均已预订,并且我们假设已付款,因此我们不能简单地更改座位数量。所以我们阻止了更改。 将错误通知用户。

您由此确保在座位都被预订时无法减少座位数。但是因为你必须能够真正改变它们,就像我们的(愚蠢的)现实一样,因为例如。火灾,您还必须为此制定一些机制。就个人而言,我可能会为它制作一个新的命令+事件,其中涉及对客户的一些回报和一些道歉。

我希望这对你有所帮助。

【讨论】:

哇,感谢这些 inupts 家伙,给我几天时间来咀嚼它们,然后重新开始。

以上是关于CQRS / 事件溯源 / 事件总线 / 时序的主要内容,如果未能解决你的问题,请参考以下文章

CQRS 和事件溯源指南

使用事件溯源和 CQRS 的缺点是啥?

事件溯源/CQRS 读取模型 - 预测

用示例程序介绍CQRS和事件溯源机制

「事件驱动架构」事件溯源,CQRS,流处理和Kafka之间的多角关系

Akka.NET 中的事件溯源和 CQRS