在事件采购中,写入系统的事件是否描述了对多个对象的更改?我试图理解如何在N个项目之间记录更改,而不会因其他编写者而面临并发问题。
背景
我的系统中有两种类型的设置:全局设置和本地设置。
只有一个全局设置对象。这由系统中的一小部分管理员控制。有N"本地"设置对象。这些都从全局设置继承了它们的可用选项。主持人使用这些来控制其页面上的选项。
为了进一步说明,全局设置对象指定了本地设置的内容。例如
+------------------------+ +----------------------------+
| GLOBAL | | LOCALL |
| | | |
| Avalable Attributes: | | |
| custom_css: show | | custom_css: enabled |
| something_else: show | | something_else: disabled |
| | | |
+------------------------+ +----------------------------+
因此,在全局设置中声明的属性基本上类似于本地设置可以配置的功能切换。因此,这是我的问题的核心,全局设置中的更改必须级联到N本地设置。由于审计要求(谁改变了什么/何时),级联变更是一项艰难的要求。即主持人必须能够了解他们之前没有删除过的选项。
在传统设置中,这将在交易中完成。但是,如果不能以原子方式记录不同项目的事件,您将如何在事件采购中执行此操作?如果没有事务处理,我们就会遇到主持人修改本地设置(可能)的问题,而从管理员到全局配置的写入仍在写入日志。
因此,日志会显示一个本地设置,启用选项,因为它已被删除,所以不应该。
+----------------------------+------------------------+------------+
| User: Admin | User: bob(moderator) | INVALID |
| RemovedOption: custom_css | custom_css: True | STATE!! |
+----------------------------+------------------------+------------+
我能想到解决这个问题的唯一方法是:
:一种。写一个描述"交易"
的复合事件+----------------------------------------+
| SettingsChangedCompositeEvent |
| ----------------------------- |
| events:[ |
| {Global: {RemovedOption: custom_css}}, |
| {Local1: {RemovedOption: custom_css}}, |
| {Local2: {RemovedOption: custom_css}}, |
| ... |
| {LocalN: {RemovedOption: custom_css}} |
|
| ] |
| |
+----------------------------------------+
这似乎是完全错误的,因为它需要一种完全不同的处理方式(至少根据我目前对事物通常的看法)。保湿本地设置时,我必须具有特殊逻辑,可以从事件的更改列表中获取相关更改(如果存在)并有条件地应用它。
B中。根本不尝试级联写入,而是在读取时合并它们
停止尝试将N本地设置对象与一个全局设置保持同步,而是始终加载全局设置的状态以及本地设置的状态以进行验证(是我'我试图在Local中切换仍然存在于Global?中),以及用于表示(以便最终聚合对象是Global和Local的组合)
我认为,这种设置会遇到与尝试在同一日志中保持所有内容同步的相同并发问题。即,用户可以在管理员用户移除事件正在作用的选项的同时触发事件以更新其本地设置。
℃。还有什么?
我真的不确定如何处理紧密绑定在一起的数据。处理基本上基于继承的数据模型的正确方法是什么?
答案 0 :(得分:1)
在事件采购中,写入系统的事件是否描述了对多个对象的更改?
是的,可能;这取决于你如何在一致性边界内建模。
在域驱动设计中,Eric Evans谈论“聚合”;关于聚合是的内容有很多不同且令人困惑的解释,但其中一个核心事实是:聚合中的所有元素都存储在中。
因此,在全局设置中声明的属性基本上类似于本地设置可以配置的功能切换。因此,这是我的问题的核心,全局设置的更改必须级联到N本地设置。
因此,为了实现在一个“事务”中更改所有这些设置的模拟,您将设计模型,以便所有这些都是同一“聚合”的一部分,并且您将所有事件存储在同一个“聚合”中事件流。
如果并发编辑很少,那就没问题了。当并发编辑发生时,通常的答案是让竞争中的输家重试(自动),如果引入冲突,那么命令失败并允许客户端重新加载状态并找出要做的事情。
在事件流中,您可以拥有描述系统单个元素更改的事件,适用于所有元素的更改,适用于元素子集的更改,这一切都很好。请记住,事件只是描述状态变化的消息 - 您可以将它们设置为对您的域有意义的精细或粗略。
当单个事务表示为多个事件时,重要的是所有事件都写入。
List<Event> oldHistory = eventStore.get(key)
List<Event> newHistory = doSomethingInterestingWith(oldHistory)
eventStore.compareAndSwap(key, oldHistory, newHistory)
答案 1 :(得分:1)
首先要挑战你最初的假设,即使用最终的一致性还不够好。
您可以创建一个事件处理程序来侦听来自GlobalSettings的事件,然后向每个受影响的LocalSettings发送命令。因此,最终(通常很快)将更改传播到所有LocalSettings。如果在原始全局更改与传播之间的短窗口中进行本地设置更改,则LocalSettings的审计跟踪/事件流将显示该更改,然后是GlobalSettings相关事件。即使最初的GlobalSettings事件在现实世界中首先发生,您也不需要显示,因为这不是特定聚合的情况。此外,用于保存GlobalSettings更改的UI可以以某种方式指示更改已完全传播的时间,如果您希望它对用户来说似乎是原子的。
这类似于向管理者讲述新业务规则的现实场景,然后她需要一段时间来通知所有下属 - 只要每个人都能快速获得新规则,这是正常的并且通常不会出现问题然后坚持下去。
另一个选项是只有一个设置事件流,并使用快照使其可管理。但是,这会限制并发性和可伸缩性,因此它取决于您的要求。