6

所以,我正在做一个 CQRS/ES 项目,在这个项目中,我们对如何处理在其他架构中很容易处理的琐碎问题有一些疑问

我的情况如下:

我有一个客户 CRUD REST API,每个客户都有唯一的文档(编号),所以当我注册一个新客户时,我必须验证是否有另一个客户使用该文档以避免重复,但是当涉及到 CQRS/ ES 架构最终实现了一致性,我发现这种验证很难解决。

需要注意的是,我的问题不是跨微服务,而是在同一个微服务的命令应用和查询应用之间。

我们也在使用eventstore

我目前的解决方案:

所以我今天要做的是,在我的命令应用程序中,在保存 CustomerCreated 事件之前,我询问查询应用程序(使用 PostgreSQL)是否有该文档的客户,如果没有,我允许该事件继续进行。但这并不能保证 100%,对吧?因为我的查询可以去同步,所以我不能 100% 信任它。那是我的第二次验证开始的时候,当我的查询应用程序正在处理事件并将它们保存到我的 PostgreSQL 时,我再次检查该文档是否有客户,如果有,我拒绝该事件并发出一个补偿事件以撤消/cancel/inactivate 使用重复文档的客户,从而在 eventstore 上完成该客户流。

尽管这可行,但这里有两件事困扰着我,第一件事是我的命令应用程序依赖于查询应用程序,所以如果我的查询应用程序关闭,我的命令就会受到影响(今天,如果查询是,我只是在验证时返回 false下降但仍然......)第二件事是,查询/读取模型真的应该能够发出事件吗?如果是这样,正确的做法是什么?该命令是否应该为此提供某种 API?或者查询是否应该使用一些公共共享库将事件直接发送到事件存储?如果我有多个视图/阅读?我应该选择哪一个来处理这个?

真的希望有人能阐明这些问题并帮助我解决这些问题。

4

3 回答 3

4

作为参考,您可能需要查看 Greg Young 所写的关于Set Validation的内容。

我询问查询应用程序(使用 PostgreSQL)是否有该文档的客户,如果没有,我允许该事件继续进行。但这并不能保证 100%,对吧?

完全正确 - 您的读取模型是陈旧的副本,并且可能没有写入模型收集的所有信息。

那是我的第二次验证开始时,当我的查询应用程序正在处理事件并将它们保存到我的 PostgreSQL 时,我再次检查该文档是否有客户,如果有,我拒绝该事件并发出一个补偿事件以撤消/cancel/inactivate 使用重复文档的客户,从而在 eventstore 上完成该客户流。

这种拼写与通常的设计不太匹配。更常见的实现是,如果我们在读取数据时检测到问题,我们会向写入模型发送一条命令消息,告诉它理顺事情。

这通常被称为流程管理器,但您可以将其视为系统人工主管的自动化。从概念上讲,流程管理器是要发送到命令模型的消息的事件源集合。

您可能还需要考虑是否正确地对域进行建模。如果文档应该是唯一的,那么命令模型可能应该使用文档编号作为记录簿中的键,而不是使用客户。或者文档 ID 应该是客户数据的函数,而不是任意输入。

据我所知,eventstore 没有跨不同流的事务

对 - 一般来说,您真正需要考虑的一件事是您的流边界在哪里。如果集合验证具有重要的业务价值,那么您确实需要考虑将整个集合放入单个流中(或者通过找到一种方法来限制唯一性而不使用集合)。

我应该如何向写入模型发送命令消息?通过 API?通过像 Kafka 这样的消息代理?

那是管道;只要您确定该命令在其自己的事务/工作单元中运行,您如何执行它并不重要。

于 2018-03-23T14:44:23.750 回答
4

所以我今天要做的是,在我的命令应用程序中,在保存 CustomerCreated 事件之前,我询问查询应用程序(使用 PostgreSQL)是否有客户使用该文档,如果没有,我允许该事件继续进行。但这并不能保证 100%,对吧?因为我的查询可以去同步,所以我不能 100% 信任它。

不,您不能安全地依赖最终一致的查询端来防止系统步入无效状态。

你有两个选择:

  1. 您允许系统进入一个临时的、未决的状态,然后,最终,您将把它带入一个有效的永久状态;为此,您可以允许命令传递,产生CustomerRegistered事件并使用 Saga/Process 管理器验证针对唯一索引的文档集合并发出补偿命令(不是事件!),即UnregisterCustomer.

  2. 您无需发送命令,而是创建并启动一个 Saga/Process,它将文档预分配到一个唯一索引的文档集合中,如果成功则发送RegisterCustomer命令。您可以将 Saga 建模为实体。

因此,在这两种解决方案中,您都使用 Saga/Process 管理器。为了使系统具有弹性,您应该确保该RegisterCustomer命令是幂等的(因此如果 Saga 失败/重新启动,您可以重新发送它)

于 2018-03-23T15:06:23.920 回答
1

你遇到了一个相当普遍的问题。我认为 VoicOfUnreason 的另一个答案值得一读。我只是想让你知道更多的选择。

  1. 我过去使用的一种简单方法是创建一个查找表。您的命令尝试在唯一约束表中注册键。如果它可以保留密钥,则命令可以继续。

  2. 根据数据的性质和领域,您可以让这个“问题”发生并引发额外的事件来标记它。如果它对业务/应用程序的工作方式很重要,那么您可以手动或通过补偿命令同时处理它。如果是后者,那么使用流程管理器是有意义的。

  3. 在一些(罕见的)速度/容量不是问题的情况下,您可以考虑使用老式的锁定和事务。诚然,这些更适合 CRUD 风格的实现,但它们可以在 CQRS/ES 中使用。

我在我的博客文章中有更多详细信息:如何在 CQRS 中处理基于集合的一致性验证

我希望你觉得这对你有帮助。

于 2018-03-23T15:29:06.780 回答