Event Sourcing时,事件是否包含对当前对象及其依赖关系的更新?

When Event Sourcing, does an event encompase the updates to the current object as well as its dependent relations?

在事件溯源中,写入系统的事件是否描述了对多个 对象的更改?我试图了解如何记录 N 个项目的更改,而不会因其他作者而冒并发问题的风险。

背景

我的系统有两种设置:全局设置和本地设置。

只有一个全局设置对象。这由系统中的一小组管理员控制。有 N "local" 个设置对象。这些都从全局设置中继承了它们的可用选项。主持人使用这些来控制其页面上的选项。

为了进一步说明,全局设置对象指定了本地设置中的内容。例如

+------------------------+    +----------------------------+
| GLOBAL                 |    |  LOCALL                    |
|                        |    |                            |
| Avalable Attributes:   |    |                            |
| custom_css: show       |    |  custom_css: enabled       |
| something_else: show   |    |  something_else: disabled  |
|                        |    |                            |
+------------------------+    +----------------------------+

因此,在全局设置中声明的属性本质上类似于本地设置可以配置的功能切换。因此,这是我问题的核心,全局设置中的更改必须级联到 N 个本地设置。由于审计要求(谁更改了what/when),级联更改是一项硬性要求。即版主必须能够看到为什么他们以前没有删除的选项。

在传统设置中,这将在事务中完成。但是,如果不能自动记录不同项目的事件,您将如何在事件溯源中执行此操作?如果没有事务,我们会遇到本地设置(可能)被主持人修改的问题,而从管理员到全局配置的写入仍在写入日志。

因此,日志将显示一个本地设置启用一个选项他们不应该因为它已被删除。

+----------------------------+------------------------+------------+
|  User: Admin               |  User: bob(moderator)  |   INVALID  |
|  RemovedOption: custom_css |  custom_css: True      |   STATE!!  |
+----------------------------+------------------------+------------+

我能想到的解决这个问题的唯一方法是:

一个。编写一个描述 "Transaction"

的复合事件
+----------------------------------------+
| SettingsChangedCompositeEvent          |
| -----------------------------          |
| events:[                               |
| {Global: {RemovedOption: custom_css}}, |
| {Local1: {RemovedOption: custom_css}}, |
| {Local2: {RemovedOption: custom_css}}, |
| ...                                    |
| {LocalN: {RemovedOption: custom_css}}  |
|
| ]                                      |
|                                        |
+----------------------------------------+

这似乎是完全错误的,因为它需要一种完全不同的处理方式(至少根据我目前对通常做事方式的看法)。在对本地设置进行保湿时,我必须有特殊的逻辑,可以从事件的更改列表中提取相关更改(如果存在)并有条件地应用它。

乙。根本不要尝试级联写入,而是在读取时合并它们

停止尝试使 N 个本地设置对象与一个全局设置对象保持同步,而是始终加载全局设置的状态以及本地设置的状态以进行验证(我正在尝试切换 Local still present in Global?),并进行演示(以便最终聚合对象是 Global 和 Local 的组合)

我认为,这种设置遇到了与试图在同一日志中保持所有内容同步相同的并发问题。即,用户可以触发事件以更新其本地设置,同时管理员用户删除事件正在执行的选项。

C。还有别的吗?

我真的不确定如何处理像这样紧密绑定在一起的数据。处理基本上基于继承的数据模型的正确方法是什么?

In Event Sourcing, do Events written to the system ever describe the changes to multiple objects?

是的,有可能;这取决于您如何在一致性边界内建模。

在领域驱动设计中,Eric Evans 谈到"aggregates";关于聚合 是什么 有很多不同且令人困惑的解释,但其中一个核心事实是:聚合中的所有元素都 存储在一起 .

So, attributes declared in the Global Settings are essentially like feature toggles for what the Local settings can configure. As such, and this is the core of my problem, a change in the Global Settings must cascade down to N Local settings.

因此,为了实现在一个 "transaction" 中更改所有这些设置的模拟,您将设计您的模型,以便所有这些都是同一 "aggregate" 的一部分,并且您将存储所有同一事件流中的事件数。

如果并发编辑很少见,那就没问题了。当并发编辑发生时,通常的答案是让比赛中的失败者重试(自动),如果这引入了冲突,则命令失败并允许客户端重新加载状态并确定要做什么。

在事件流中,您可以拥有描述对系统单个元素的更改、应用于所有元素的更改、应用于元素子集的更改的事件,这一切都很好。请记住,事件只是描述状态变化的消息——您可以根据您的域使它们变得精细或粗糙。

当单个事务表示为多个事件时,重要的是写入所有或 none 个事件。

List<Event> oldHistory = eventStore.get(key)
List<Event> newHistory = doSomethingInterestingWith(oldHistory)
eventStore.compareAndSwap(key, oldHistory, newHistory)

首先要挑战您最初的假设,即使用最终一致性还不够好。

您可以创建一个事件处理程序来侦听来自 GlobalSettings 的事件,然后向每个受影响的 LocalSettings 发送命令。因此,最终(通常很快)更改会传播到所有 LocalSettings。如果您在原始全局更改和传播之间的短 window 内更改了本地设置,LocalSettings 的审计 trail/event 流将显示该更改,然后是 GlobalSettings 相关事件。即使最初的 GlobalSettings 事件首先发生在现实世界中,您也不需要证明这一点,因为对于这个特定的聚合来说情况并非如此。此外,用于保存 GlobalSettings 更改的 UI 可以以某种方式指示更改何时已完全传播,如果您希望它对用户来说是原子的。

这类似于将新业务规则告知经理然后她花了一些时间通知所有下属的真实场景 - 这很正常,只要每个人都得到新的,通常不会有问题快速规则,然后坚持下去。

另一种选择是只有一个设置事件流,并使用快照使其易于管理。但是,这会限制并发性和可扩展性,所以这取决于您的要求。