CQRS多线程查询处理

时间:2018-04-18 05:35:44

标签: cqrs

我们正在构建基于CQRS的应用程序。对于大多数信息,Command和Query共享同一个表,但对于一些重读数据,我们计划使用单独的查询表来避免多个连接。命令部分将更新其数据然后发布事件,查询部分将监听这些事件并更新自己的数据"最终"。现在问题/问题是命令部分将在多线程环境中执行,即多个用户将并行处理不同的数据。但是Query部分必须以顺序方式处理这些事件,因为事件的顺序很重要,因此它不能是多线程的,您可以为不同类型的事件使用不同的线程,但只有一个线程可以处理单一类型的事件。在那种情况下,这种架构的规模如何?或者我错过了什么?

1 个答案:

答案 0 :(得分:4)

  

我们正在构建基于CQRS的应用程序。对于大多数信息,Command和Query共享同一个表,但是对于一些重读数据,我们计划使用单独的查询表来避免多个连接。

因此,这种架构不是CQRS,因为分离并不完整。要成为CQRS,您需要拥有单独的模型甚至数据库/表/集合。

  

但是Query部分必须以顺序方式处理这些事件,因为事件的顺序很重要,所以它不能是多线程的

让我们谈谈我们可以拥有什么。

有关事件排序的两种可能性:

  1. Read模型期望事件按总顺序排列;这意味着Read模型的代码希望按照生成的顺序处理事件。这意味着事件需要具有某种全局序列号(整数或时间戳,如MongoDB的Timestamp,每个实例都是唯一的)。

    • 专业人士:更简单的阅读模型代码
    • 缺点:限制Write模型的缩放,因为全局排序需要编写者之间的强烈同步
  2. Read模型不希望事件处于总体顺序,单个Stream中的排序就足够了; Stream是具有多种类型的事件集合,由单个实体(如DDD中的聚合)生成,这些类型是有序的。也就是说,Stream中的事件始终处于正确的顺序。

    • 专业人士:在架构的写入方面具有非常好的可扩展性,编写器(机器,节点)之间没有全局同步
    • 缺点:复杂的读取模型代码,因为它必须重新排序或等待事件,直到它更新其可见数据
  3. 在这两种情况下,Read模型仅消耗单一类型的事件。读模型可能会消耗许多类型的事件,通常它们会消耗。想象一下活跃用户列表阅读模型:它在虚构的用例中需要UserCreatedUserActivatedUserDeleted事件类型。

      

    ...因为事件的顺序很重要,所以它不能是多线程的......

    如果您找到可以剪切的维度,则可以是多线程/缩放。如果事件类型是由单个实体类型生成的,那么您可以通过使用ID的哈希来按实体ID拆分读模型工作者;所有工作人员都可以更新Read-model的数据库,而不会踩到彼此的脚,因为每个人都更新了不同的实体。

    在我们虚构的用例中,您可以通过UserId进行拆分。因此,每个读模型工作者都会得到一个应该处理的ID范围。例如,工作人员1可以处理从1到100的ID,第二个工作人员可以处理从101到200的ID,依此类推。当一个读模型工作者获得一个事件(通过提取,轮询,发布/订阅,无论什么意思),它从中提取UserId(即event.getUserId()),如果它与指定的范围不匹配,它只是忽略它

    在更复杂的情况下,当您需要来自不同实体类型的事件时,您仍然可以扩展。就像在上面的例子中一样,你只需要找到一个可以切割的维度。但在这种情况下,将会有没有该维度的事件类型。 这些事件将由所有工作人员处理,也就是说,他们不会被忽略

    在我们虚构的情况下,当事件来自UserAggregate时,您会被UserId分割,但当事件来自RoleAggregate时(是的,您猜对了,这是一个身份验证)和授权有界上下文),它没有UserId属性,所以它由Read模型处理,不被拒绝。

    所以,这一切都减少到选择维度。通常,维度是主要实体的ID,因为许多读取模型实际上是实体列表,附加了非规范化数据。例如,我们虚构的List-of-active-users Read-model是一个用户列表,其中没有停用,并且还有与之关联的角色。

    这些模式也可以在某种程度上应用于Sagas /流程经理。