繁体   English   中英

在DDD,CQRS,EventSourcing中出现错误的,不一致的事件流中的逻辑异常?

[英]Logic exception in a buggy incoherent stream of events in DDD, CQRS, EventSourcing?

假设您使用EventSourcing接近DDD。

我们都知道事件是不可变的,因此永远不要从事件日志中删除它们。 但是,如果流在逻辑上是“不正确的”怎么办? 不是那种经典的情况:“我加了钱,我不必加钱,所以创建一个补偿事件来取钱。”

我不是在谈论运行时异常,而是在事件流中可能会发现的逻辑异常 ,因为编码人员在事件编写器中犯了错误。

如果编写事件流的软件包含违反域逻辑的错误,如何“重放”事件流?

Oookay ...我们都知道“那本不该发生的”和“解雇编写这些事件编写器的编码器”等等。

但是,让我们假设事件流在那里,并且您正在重建重播所有流的投影。 只是有可能发生,您将被告知从现有事件流中重建预测。

突然之间,当重播事件流时,您会发现不符合当前业务规则或当时存在的规则的“不一致”事件。

例子1

您有以下事件:

#  TimeStamp  Event          Data
------------------------------------------------------
1  03/jul     car.created    { id: 4444, color: blue }
2  14/jul     car.delivered  { id: 4444, to: Alice }
3  18/jul     car.created    { id: 5555, color: blue }
4  22/jul     car.created    { id: 5566, color: orange }
5  25/jul     car.created    { id: 5577, color: blue }

7月26日,有人问:“您有几辆蓝色汽车?”。

透明:2个单位(编号55555577 )。

原因: 4444单元已售出。 单位5566为橙色。

但是,如果您有这个越野车序列怎么办?

#  TimeStamp  Event          Data
------------------------------------------------------
1  03/jul     car.created    { id: 4444, color: blue }
2  14/jul     car.delivered  { id: 4444, to: Alice }
3  18/jul     car.created    { id: 5555, color: blue }
4  22/jul     car.created    { id: 5566, color: orange }
5  23/jul     car.created    { id: 5555, color: red }
6  25/jul     car.created    { id: 5577, color: blue }

当然,事件5永远都不会发生,您不能创建相同的单元2次。

在调查领域专家之后,您发现事件5不正确。 它应该显示为“ car.repainted”,但该软件存在问题,并编写了“ car.created”。

示例问题1:

  • 您是否会添加新事件编号为7或更多的事件,并在事件5之后加上时间戳记,以进行某种补偿? 您会写哪些事件?
  • 您是否将添加新事件编号为7或更多,且时间戳为“事件之前”事件5,以向重播“嘿,忽略下一个创建”发出某种信号? 您会写哪些事件?
  • 您是否会重写您的“重播器”,以便他们可以解释为“ 25 / jul之前的任何东西都是“双重创建”的意思是“ car.repainted”,然后重新运行重播器以重建聚合?
  • 您会违反黄金法则并“触摸”历史吗? 实际上,这不是“历史”,因为事件“ 5”并未真正发生。 那我们可以触摸吗?

例子2

假设有一个带叉车的仓库,用来从架子上取货。 仓库包含2条垂直走廊,2条水平走廊和1条对角走廊。

所有走廊都是双向的,除了左边的垂直走廊有某种台阶或其他形状,而叉车只能从A移到C,而不能反向移动。 并且从下面的水平线也有台阶,而叉车只能从D移至C,而不能从C移至D。

购买机器后,您每天都从A点开始,那里是仓库的进口门。 无论这个例子在一天结束时叉车如何消失,都不在乎。

这些命令可以是:

purchase()
start()
goRight()
goLeft()
goUp()
goDown()
cross()

事件可以是:

purchased
started
wentRight
wentLeft
wentUp
wentDown
crossed

这是叉车聚合的可能状态图:

事件采购汇总状态图

假设您正在重放聚合的事件,并且发现了这些事件:

#   TimeStamp     Event
----------------------------------------------
1   12/jul 10:00  purchased
2   14/jul 09:00  started
3   14/jul 11:00  wentDown
4   14/jul 12:00  crossed
5   14/jul 14:00  wentDown
6   23/jul 09:00  started
7   23/jul 10:00  wentRight
8   23/jul 13:00  crossed

有人问:“叉车现在在哪里?您可以轻松地告诉“ C”。

原因:以前不管发生什么事6 ,因为事件6重置定位A ,事件7朝着移动B ,事件8向移动C

但是,如果序列继续这样下去怎么办?

#   TimeStamp     Event
----------------------------------------------
[...]
6   23/jul 09:00  started
7   23/jul 10:00  wentRight
8   23/jul 13:00  crossed
9   23/jul 15:00  wentUp
10  23/jul 16:00  wentRight
11  27/jul 09:00  started
12  27/jul 11:00  wentDown

某个领域专家问您:“嘿,极客,您告诉我们事件外包是不可思议的:7月23日18:00的叉车在哪里?”

我们都知道电梯不能“跳上楼梯”,所以我们都知道事件9永远不会发生。

因此,我们的“重播器”无法做其他抛出异常的事情。 但是已经写入的事件序列就是那个。

这里的主题不是“如何编写一个好的序列”,而是“面对带有异常的序列时该怎么办”。

示例2的问题:

  • 你会写一个补偿事件吗? 怎么样? 哪一个? 什么时候?
  • 您会重写历史记录吗? (如果您有数百万个事件,则很丑)
  • 从域事件重播器的角度来看,您将如何处理该异常?

如果编写事件流的软件包含违反域逻辑的错误,如何“重放”事件流?

您至少有两个选择:

  1. 在Aggregate apply方法或事件订阅者中添加一些修复代码(Readmodels,projections,Sagas); 这段代码应该处理您要避免的确切情况。

它的缺点是它将永久存在于代码库中,但是它的优点是可以在零停机时间内完成。

  1. 迁移事件存储。 格雷格·杨(Greg Young)有一关于如何做的 基本上,您可以在可能的另一个事件存储实例上创建另一个事件流,处理该事件流中的每个事件,修复异常并追加到新的事件流中。 迁移完成后,您将旧的事件存储替换为新的事件存储。

这样做的缺点是,替换事件存储区时可能需要一些停机时间,但是这样做的好处是,您可以“忘记”错误,从而获得干净/正确的事件流。

你会写一个补偿事件吗? 怎么样? 哪一个? 什么时候?

当您需要快速解决方案时,编写补偿事件非常方便。 这是解决方案编号的特例。 1。

您会违反黄金法则并“触摸”历史吗? 实际上,这不是“历史”,因为事件“ 5”并未真正发生。 那我们可以触摸吗?

您一定可以这样做,因为我确实想获得最快的解决方案,但是根据框架/技术的不同,它可能很难看。 例如,订阅者无法再确定他们是否处理了事件存储中的所有相关事件,因此,为了确定,您需要重建所有Readmodels; Sagas可能会遇到的最大问题是,处理事件会产生副作用。

关于法律方面,如果您确实修改了历史记录,则需要存档旧的事件流,以防万一有人要求它。 这取决于您的域。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM