偶尔连接CQRS系统

时间:2014-08-09 20:31:08

标签: domain-driven-design cqrs event-sourcing occasionallyconnected get-event-store

问题:

两名员工(A& B)同时离线,同时编辑客户#123,比如版本#20,离线时继续进行更改......

方案:

1 - 两名员工编辑客户#123并对一个或多个相同的属性进行更改。

2 - 两名员工编辑客户#123,但不要做出相同的更改(他们互相交叉而不接触)。

......然后他们都回到网上,第一名员工A追加,从而将客户更改为版本#21,然后将员工B更改为版本#20

问题:

我们在方案1中保留了哪些变化?

我们可以在方案2中进行合并,怎么做?

上下文

1 - CQRS +事件采购风格系统

2 - 使用事件源Db作为队列

3 - 阅读模型的最终一致性

4 - RESTful API

diagram for the visually inclined; it's mash-up of a MS diagram and a few things changed

EDIT-1:基于目前答案的澄清:

为了执行精细的粒度合并,我需要为表单中的每个字段设置一个命令,例如?

enter image description here

上面,ChangeName,ChangeSupplier,ChangeDescription等的细粒度命令,每个都有自己的时间戳,可以在A&事件中自动合并。 B都更新了ChangedName?

编辑-2:根据特定事件存储的使用情况进行跟进:

似乎我会利用@GetEventStore来保持事件流的持久性。

他们使用乐观并发如下:

  • 流中的每个事件都会将流版本增加1

  • 写入可以指定预期版本,在编写器上使用ES-ExpectedVersion标头

    • -1指定流不应该已存在

    • 0及以上指定流版本

    • 如果流不在版本中,写入将失败,您要么使用新的预期版本号重试,要么重新处理该行为并确定如果您愿意选择它就行。

  • 如果未指定ES预期版本,则禁用乐观并发控制

  • 在此上下文中,乐观并发不仅基于消息ID,还基于事件#

4 个答案:

答案 0 :(得分:5)

如果我正确理解了您的设计图片,那么偶尔连接的用户将命令排队,即更改请求,以及当用户重新连接排队的命令时一起发送;只有一个数据库权限(命令处理程序查询加载其最新版本的aggretates);只有视图模型才会同步到客户端。

在此设置中,场景2 可以通过您的设计轻松自动合并,如果您明智地选择命令,请阅读:使它们细粒度:对于每一种可能更改,选择一个命令。然后,在重新连接客户端时,命令以任何顺序处理,但由于它们只影响析取字段,所以没有问题:

  1. 客户在v20。
  2. A处于离线状态,根据v20的陈旧模型编辑更改。
  3. B处于离线状态,根据v20的陈旧模型编辑更改。
  4. A联机,批量发送排队的ChangeName命令,v20的客户已加载并保留为v21。
  5. B联机,批量发送排队的ChangeAddress命令,v21的客户已加载并保留为v22。
  6. 数据库包含具有正确名称和地址的用户,如预期的那样。
  7. 场景1 中,使用此设置,两名员工都将覆盖其他员工的更改:

    1. 客户在v20。
    2. A处于离线状态,根据v20的陈旧模型编辑更改。
    3. B处于离线状态,根据v20的陈旧模型编辑更改。
    4. A联机,批量发送排队ChangeName命令给“John Doe”,v20的客户加载并保存为v21,名称为“John Doe”
    5. B联机,批量发送排队的ChangeName命令到“Joan d'Arc”,v21的客户(名为“John Doe”)被加载并保存为v22(名称为“Joan d'Arc” “)。
    6. 数据库包含名为“Joan d'Arc”的用户。
    7. 如果B在A之前上线,则反之亦然:

      1. 客户在v20。
      2. A处于离线状态,根据v20的陈旧模型编辑更改。
      3. B处于离线状态,根据v20的陈旧模型编辑更改。
      4. B上线,批量发送排队的ChangeName命令到“Joan d'Arc”,v20的客户被加载并保存为v21(名称为“Joan d'Arc”)。
      5. A联机,批量发送排队的ChangeName命令给“John Doe”,v21的客户加载并保存为v22,名称为“John Doe”。
      6. 数据库包含名为“John Doe”的用户。
      7. 有两种方法可以启用冲突检测:

        1. 检查命令的创建日期(即员工修改的时间)是否 Customer的最后修改日期之后。这将禁用方案2的自动合并功能,但会针对并发编辑提供完整的冲突检测。
        2. 检查命令的创建日期(即员工修改的时间) 个人字段的最后修改日期之后的 Customer它会改变。这将使场景2的自动合并保持不变,但会在场景1中为您提供自动冲突检测。
        3. 两者都很容易通过事件源实现(因为事件流中各个事件的时间戳可能已知)。

          关于你的问题“我们在方案1中保留了哪些变化?” - 这取决于您的业务领域及其要求。

          EDIT-1:回答澄清问题:

          是的,对于每个可以单独更改的字段(或字段组),您将需要一个命令。

          关于你的模型:你所展示的是典型的“CRUD”UI,即多个表单字段,例如一个“保存”按钮。 CQRS通常并且自然地与“基于任务”的UI组合,其中例如将显示Status字段(只读),并且如果用户想要改变状态,则点击一下,比如说,“更改状态”按钮,用于打开对话框/新窗口或其他UI元素,可以更改状态(在基于Web的系统中,就地编辑也很常见)。如果您正在执行“基于任务”的UI,其中每个任务仅影响所有字段的一小部分,那么ChangeName,ChangeSupplier等的细粒度命令是很自然的。

答案 1 :(得分:4)

以下是一些解决方案的概述:

场景1

有人必须决定,最好是人。您应该询问用户或显示存在冲突。

Dropbox通过选择后一个文件并将file.conflict文件保存在同一目录中以供用户删除或使用来解决此问题。

场景2

保留原始数据并查看实际更改的字段。然后,您可以应用员工1的更改,然后应用员工2的更改,而无需踩任何脚趾。

场景3(仅当更改在不同时间联机时)

让第二个用户知道他们离线时有变化。尝试场景2并向第二个用户显示新结果(因为这可能会改变他的输入)。然后问他是否要保存他的更改,先修改它们,或将它们丢弃。

答案 2 :(得分:0)

Aaron,事件确实发生冲突,即在场景1中,我会期望抛出某种并发异常。

第二种情况更有趣。假设您的命令和事件定义合理,即不是CRUD的包装器,那么您将能够测试自发出命令以来发生的事件是否实际冲突。为此,我使用并发冲突注册表。基本上当我检测到潜在冲突时,我会抓取自我当前版本以来已提交的事件,并要求注册表检查其中是否存在任何冲突。

如果你想看一个代码示例,并且有更多详细信息,我将一篇文章概述为我的方法。请在此处查看:handling concurrency issues in cqrs es systems

希望这有帮助!

答案 3 :(得分:0)

在这种情况下,也许您可​​以使用"聚合根"概念,用于由CEP Engine(复杂事件处理引擎)驱动的项目,以执行这些复杂的操作。