DDD,CQRS,EventSourcing中错误的不连贯事件流中的逻辑异常?

问题描述 投票:1回答:1

假设您使用EventSourcing接近DDD。

我们都知道事件是不可变的,它们永远不应该从事件日志中删除。但是,如果流在逻辑上“不正确”怎么办?不是那种经典的案例,“我加了钱,我没有必要添加它,所以创建一个补偿事件来撤回它。”

我不是在讨论运行时异常,而是在事件流中可能找到的逻辑异常,因为编码器在事件编写器中产生了错误。

Question

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

Oookay ......我们都知道“那应该永远不会发生”和“解雇编写这些事件编写者的程序员”等等......

但是我们假设事件流就在那里,你正在重建重放所有流的投影。只是它可能已经发生,并且您被告知要重建现有事件流的投影。

突然间,在重放事件流时,您会发现不符合当前业务规则的“不连贯”事件,或者当时存在的规则。

Example 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 }

在26岁左右,有人问道:“你有多少辆蓝色汽车?”

晶莹剔透:2个单位(ids 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”。

Question for example 1:

  • 您是否会添加编号为7及更多的新事件,时间戳“紧跟”事件5之后,以进行某种补偿?你会写哪些事件?
  • 你会添加7号或更多的新事件,时间戳“就在事件5之前”,向“嘿,忽略下一个创作”的重放者发出某种信号吗?你会写哪些事件?
  • 你会改写你的“重播者”,这样他们就可以解释“25岁以后的任何事情都是”双重创造“意味着”car.repainted“并重新运行重建者来重建聚合体吗?
  • 你会违反黄金法则并“触及”历史吗?事实上它不是“历史”,因为事件“5”并没有真正发生。我们可以触摸它吗?

Example 2

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

所有走廊都是双向的,除了左侧垂直走廊有一些台阶或其他什么,叉车只能从A移动到C而不是倒车;除了从下面还有台阶的水平面以外,叉车只能从D移动到C,从不从C移动到C.

购买机器后,您每天都在现场A开始,因为仓库的入口就在那里。无论如何在一天结束时这个例子叉车刚刚消失,不在乎。

命令可以是:

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

事件可以是:

purchased
started
wentRight
wentLeft
wentUp
wentDown
crossed

这是叉车骨料的可能状态图:

eventsourcing aggregate state diagram

假设您正在重播聚合的事件,您会发现:

#   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

一些领域专家问你“嘿,怪人,你告诉我们,事件采购是神奇的:叉车在18/47的18:00在哪里?”

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

所以我们的“重播者”不能做其他抛出异常的事情。但已编写的事件序列就是那个。

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

Questions for example 2:

  • 你会写一个补偿事件吗?怎么样?哪一个?什么时候?
  • 你会改写历史吗? (如果你有数百万个活动,那就太丑了)
  • 从域事件重放者的角度来看,您将如何处理该异常?
exception exception-handling domain-driven-design cqrs event-sourcing
1个回答
2
投票

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

您至少有两个选择:

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

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

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

它的缺点是,在更换事件存储时可能需要一些停机时间,但它的优点是在完成后你可以“忘记”错误,你将拥有一个干净/正确的事件流。

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

当您想要快速解决方案时,编写补偿事件非常方便;这是解决方案的特殊情况。 1。

你会违反黄金法则并“触及”历史吗?事实上它不是“历史”,因为事件“5”并没有真正发生。我们可以触摸它吗?

你可以做到这一点,我确实做到了,因为我希望尽可能快的解决方案,但它可能会变得丑陋,具体取决于框架/技术。例如,订阅者不能再确定他们是否处理了来自事件存储的所有相关事件,因此,为了确保您需要重建所有Readmodel; Sagas最大的问题是因为处理事件有副作用。

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

© www.soinside.com 2019 - 2024. All rights reserved.