在事件升级之后和修补之前,事件源中的无效状态是否可以接受?

Is it acceptable to have an invalid state in eventsourcing after event upgrading and before patching?

假设我有一个持续事件流,这些事件根据我定义的 "schema" 构建有效状态。

我更改了架构并且升级了事件以反映这一点。

但是,有些状态不能仅仅通过升级事件就生效,我还需要添加更多的事件来修补状态以使其完全有效。

首先,这个推理在事件溯源方面是否有效?

如果是这样,我该如何处理特定版本的状态不再有效的情况?我的意思是这是可以接受的吗?是否仍然可以对具有无效状态的版本进行再水化?如果这是一个写入模型并且它不是最新版本,我无论如何都无法修改这个状态所以也许这没什么大不了的?

However, some state could not be made valid just by upgrading events, I also needed to add more events to patch the state to make it fully valid.

"Compensating events"是常用词;记录簿中存在笔误,因此我们需要在历史记录中添加一个新事件来更正错误。

If so, how do I handle cases where a specific version of a state no longer becomes valid?

通常,您要小心,非常小心,不要引入任何阻止您加载无效历史记录的自动验证。请记住,状态就是状态;业务规则限制了允许域更改的方式。让损坏的状态可读,但损坏是安全的。

特别是,如果您允许加载状态,那么枚举您​​的事件流、测试对象的最终状态并为产生无效状态的任何流生成异常报告是一种直接的练习,将它们升级到 operators/management 进行处理,等等。

假设您对输入验证相当谨慎,并比较您建议的命令是否与最新的已知状态一致(聚合执行业务规则,但它们不需要为自己囤积这些规则),那么您可以可能达到足够低的错误率,您不需要积极的数据验证。当错误易于检测且修复成本低廉时尤其如此。

否则,冻结任何处于无效状态的聚合是防止进一步损坏的好方法。

但是如果你真的需要状态保持有效,你可以使用补偿事件来玩一玩。

考虑:事件溯源的基本模式类似于

History history = repository.getHistoryById(id)

State current = State.SEED

for (Event e : history) {
    current = current.apply(e)
}

这里实际上有一个隐藏的概念,它封装了在将事件传递给状态之前处理事件的逻辑。隐藏,因为 null case 只是将枚举事件直接传递给目标。

History history = repository.getHistoryById(id)
Historian historian = new Historian();

State current = State.SEED

for (Event e : historian.reviewEvents(history)) {
    current = current.apply(e)
}   

Historian 为您提供放置补偿事件逻辑的位置 - 根据其自身的状态,Historian 通过大多数事件,但修复知道需要的事件 edits/compensation/redactions

历史状态从何而来?为什么,当然是从历史学家的历史来看。您将事件更正的历史记录(通常很短)加载到历史记录中,然后让历史记录为聚合清理事件。

如果您需要对历史学家进行更正?一路下来都是乌龟!每个流都有一个独特的历史学家;历史流的标识符是从它过滤的流中计算出来的(例如,命名为 UUID 将允许您这样做)。因此,对于每个流,您检查历史流是否存在;当您找到一个不存在的对象时,您知道停止搜索并使用 null historian,汇总更改,处理最终的事件序列以重新生成真实对象的状态,然后开始。

请注意,我在任何地方都没有看到这个想法的参考实现;这是白板的声音,但事实是我在自己的设计中一直在推迟这个要求。