偶尔连接的CQRS系统 [英] Occasionally connected CQRS system

查看:99
本文介绍了偶尔连接的CQRS系统的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

问题:

两名员工(A和B)在编辑客户#123(例如版本20)的同时脱机,而在脱机状态下继续进行更改...

场景:

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

2-两名员工编辑客户#123,但不要进行相同的更改(他们彼此交叉而不接触).

...然后他们都重新上线,首先是员工A追加,从而将客户更改为版本#21,然后将客户B更改为版本20.

问题:

在方案1中,我们保留哪些变更?

我们可以在方案2中进行合并吗?

上下文:

1-CQRS +事件源样式系统

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

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

4-RESTful APIs

EDIT-1:根据到目前为止的答案进行澄清:

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

上面带有ChangeName,ChangeSupplier,ChangeDescription等的细化命令,每个命令都有自己的时间戳,可以在事件A和Amp中自动合并. B都更新了ChangedName吗?

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

似乎我将使用@GetEventStore来保持事件流的持久性.

它们利用乐观并发性如下:

  • 流中的每个事件使流版本增加1

  • 写入者可以使用编写者上的ES-ExpectedVersion标头指定期望的版本

    • -1指定流应该不存在

    • 0及更高版本指定流版本

    • 如果流不是该版本,则写入将失败,您可以使用新的预期版本号重试,或者重新处理该行为,然后确定是否可以.

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

  • 在这种情况下,乐观并发不仅基于消息ID,而且还基于事件#

解决方案

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

在此设置中,方案2 将由您的设计自动合并,如果您明智地选择命令,请阅读:使它们成为细粒度:更改,选择一个命令.然后,在重新连接客户端时,可以以任何顺序处理命令,但是由于它们仅影响分离字段,所以没有问题:

  1. 客户的版本为v20.
  2. A处于离线状态,根据过时的v20模型编辑更改.
  3. B离线,根据v20的过时模型编辑更改.
  4. A联机,批量发送排队的ChangeName命令,v20的客户已加载并保持为v21​​.
  5. B联机,批处理发送排队的ChangeAddress命令,v21的客户已加载并保持为v22.
  6. 数据库包含预期的用户名和地址.

方案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"的用户.

如果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"的用户.

有两种方法可以启用冲突检测:

  1. 检查命令的创建日期(即员工修改时间)是否在Customer的最后修改日期之后 .这将禁用方案2的自动合并功能,但将为您提供针对并发编辑的完全冲突检测.
  2. 检查命令的创建日期(即员工修改时间)是否在单个字段的最后修改日期之后要改变.这将使方案2的自动合并保持不变,但会在方案1中为您提供自动冲突检测.

两者都易于通过事件源实现(因为事件流中各个事件的时间戳可能是已知的).

关于您的问题方案1中我们保留哪些变更?" -这取决于您的业务领域及其要求.

EDIT-1:要回答的澄清问题:

是的,对于每个可以分别更改的字段(或分别为字段组),您将需要一个命令.

关于样机:显示的是典型的"CRUD" UI,即多个表单字段和一个保存"按钮. CQRS通常自然地与基于任务的" UI结合在一起,例如,将显示Status字段(只读),如果用户想要更改状态,请单击一下,例如更改状态"按钮,将打开一个对话框/新窗口或其他UI元素,您可以在其中更改状态(在基于Web的系统中,就地编辑也很常见).如果您正在执行基于任务"的UI,其中每个任务仅影响所有字段的一小部分,那么使用ChangeName,ChangeSupplier等精细命令非常合适.

Problem:

Two employees (A & B) go off-line at the same time while editing customer #123, say version #20, and while off-line continue making changes...

Scenarios:

1 - The two employees edit customer #123 and make changes to one or more identical attributes.

2 - The two employees edit customer #123 but DO NOT make the same changes (they cross each other without touching).

... they then both come back on-line, first employee A appends, thereby changing the customer to version #21, then employee B, still on version #20

Questions:

Who's changes do we keep in scenario 1?

Can we do a merge in scenario 2, how?

Context:

1 - CQRS + Event Sourcing style system

2 - Use Event Sourcing Db as a Queue

3 - Eventual Consistency on Read Model

4 - RESTful APIs

EDIT-1: Clarifications based on the answers so far:

In order to perform fined grained merging, I'll need to have one command for each of field in a form for example?

Above, finely grained commands for ChangeName, ChangeSupplier, ChangeDescription, etc., each with their own timestamp would allow for auto-merging in the event A & B both updated ChangedName?

Edit-2: Follow up based on the the use of a particular event store:

It seems as though I'll make use of @GetEventStore for the persistence of my event streams.

They make use of Optimistic Concurrency as follows:

  • Each event in a stream increments stream version by 1

  • Writes can specify an expected version, making use of the ES-ExpectedVersion header on writers

    • -1 specifies stream should not already exist

    • 0 and above specifies a stream version

    • Writes will fail if the stream is not at the version, you either retry with a new expected version number or you reprocessed the behavior and decided it's OK if you so choose.

  • If no ES-Expected Version specified, optimistic concurrency control is disabled

  • In this context, the Optimistic Concurrency is not only based on the Message ID, but also on the Event #

解决方案

If I understand your design picture correctly, then the occasionally connected users enqueue commands, i.e., change requests, and when the user reconnects the queued commands are sent together; there is only one database authority (that the command handlers query to load the most recent versions of their aggretates); only the view model is synced to the clients.

In this setup, Scenario 2 is trivially auto-merged by your design, if you choose your commands wisely, read: make them fine-grained: For every possible change, choose one command. Then, on re-connection of the client, the commands are processed in any order, but since they only affect disjunct fields, there is no problem:

  1. Customer is at v20.
  2. A is offline, edits changes against stale model of v20.
  3. B is offline, edits changes against stale model of v20.
  4. A comes online, batch sends an queued ChangeName command, the Customer of v20 is loaded and persisted as v21.
  5. B comes online, batch sends an queued ChangeAddress command, the Customer of v21 is loaded and persisted as v22.
  6. The database contains the user with their correct name and address, as expected.

In Scenario 1, with this setup, both employees will overwrite the other employees' changes:

  1. Customer is at v20.
  2. A is offline, edits changes against stale model of v20.
  3. B is offline, edits changes against stale model of v20.
  4. A comes online, batch sends an queued ChangeName command to "John Doe", the Customer of v20 is loaded and persisted as v21 with name "John Doe"
  5. B comes online, batch sends an queued ChangeName command to "Joan d'Arc", the Customer of v21 (named "John Doe") is loaded and persisted as v22 (with name "Joan d'Arc').
  6. Database contains a user with name "Joan d'Arc".

If B comes online before A, then it's vice versa:

  1. Customer is at v20.
  2. A is offline, edits changes against stale model of v20.
  3. B is offline, edits changes against stale model of v20.
  4. B comes online, batch sends an queued ChangeName command to "Joan d'Arc", the Customer of v20 is loaded and persisted as v21 (with name "Joan d'Arc').
  5. A comes online, batch sends an queued ChangeName command to "John Doe", the Customer of v21 is loaded and persisted as v22 with name "John Doe".
  6. Database contains a user with name "John Doe".

There are two ways to enable conflict detection:

  1. Check whether the command's creation date (i.e., the time of the employees modification) is after the last modification date of the Customer. This will disable the auto-merge feature of Scenario 2, but will give you full conflict detection against concurrent edits.
  2. Check whether the command's creation date (i.e., the time of the employees modification) is after the last modification date of the individual field of the Customer it is going to change. This will leave the auto-merge of Scenario 2 intact, but will give you auto-conflict-detection in Scenario 1.

Both are easy to implement with event sourcing (since the timestamps of the individual events in the event stream are probably known).

As for your question "Who's changes do we keep in scenario 1?" -- this depends on your business domain and its requirements.

EDIT-1: To answer on the clarification question:

Yes, you'll need one command for each field (or group of fields, respectively) that can be changed individually.

Regarding your mockup: What you are showing is a typical "CRUD" UI, i.e., multiple form fields and, e.g., one "Save" button. CQRS is usually and naturally combined with a "task based" UI, where there would be, say, the Status field be displayed (read-only), and if a user wants to change the status, one clicks, say, a "Change Status" button, which opens a dialog/new window or other UI element, where one can change the status (in web based systems, in-place-editing is also common). If you are doing a "task based" UI, where each task only affects a small subset of all fields, then finely grained commands for ChangeName, ChangeSupplier etc are a natural fit.

这篇关于偶尔连接的CQRS系统的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆