假设您使用EventSourcing接近DDD。
我们都知道事件是不可变的,它们永远不应该从事件日志中删除。但是,如果流在逻辑上“不正确”怎么办?不是那种经典的案例,“我加了钱,我没有必要添加它,所以创建一个补偿事件来撤回它。”
我不是在讨论运行时异常,而是在事件流中可能找到的逻辑异常,因为编码器在事件编写器中产生了错误。
如果编写它的软件包含违反域逻辑的错误,您如何“重放”事件流?
Oookay ......我们都知道“那应该永远不会发生”和“解雇编写这些事件编写者的程序员”等等......
但是我们假设事件流就在那里,你正在重建重放所有流的投影。只是它可能已经发生,并且您被告知要重建现有事件流的投影。
突然间,在重放事件流时,您会发现不符合当前业务规则的“不连贯”事件,或者当时存在的规则。
你有这些事件:
# 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 5555
和5577
)。
原因:单位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”。
我们假设一个带叉车的仓库从货架上取货。仓库包含2个垂直走廊,2个水平走廊和1个对角走廊。
所有走廊都是双向的,除了左侧垂直走廊有一些台阶或其他什么,叉车只能从A移动到C而不是倒车;除了从下面还有台阶的水平面以外,叉车只能从D移动到C,从不从C移动到C.
购买机器后,您每天都在现场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
一些领域专家问你“嘿,怪人,你告诉我们,事件采购是神奇的:叉车在18/47的18:00在哪里?”
我们都知道电梯不能“跳”过楼梯,所以我们都知道事件9永远不会发生。
所以我们的“重播者”不能做其他抛出异常的事情。但已编写的事件序列就是那个。
这里的主题不是“如何编写一个好的序列”,而是“当你面对一个有异常的序列时该怎么做”。
如果编写它的软件包含违反域逻辑的错误,您如何“重放”事件流?
您至少有两个选择:
apply
方法或事件订阅者(Readmodels,projection,Sagas)中放入一些修复代码;这段代码应该处理您要避免的确切情况。它的缺点是它将永远存在于代码库中,但它具有可以在零停机时间内完成的优点。
它的缺点是,在更换事件存储时可能需要一些停机时间,但它的优点是在完成后你可以“忘记”错误,你将拥有一个干净/正确的事件流。
你会写一个补偿事件吗?怎么样?哪一个?什么时候?
当您想要快速解决方案时,编写补偿事件非常方便;这是解决方案的特殊情况。 1。
你会违反黄金法则并“触及”历史吗?事实上它不是“历史”,因为事件“5”并没有真正发生。我们可以触摸它吗?
你可以做到这一点,我确实做到了,因为我希望尽可能快的解决方案,但它可能会变得丑陋,具体取决于框架/技术。例如,订阅者不能再确定他们是否处理了来自事件存储的所有相关事件,因此,为了确保您需要重建所有Readmodel; Sagas最大的问题是因为处理事件有副作用。
关于法律方面,如果您确实修改了历史记录,则需要将旧事件流归档以防万一有人要求它。这取决于您的域名。