具有 CQRS 和事件溯源的示例微服务应用程序
Example micoservice app with CQRS and Event Sourcing
我打算使用 CQRS 和事件溯源创建一个简单的微服务应用程序(设置和获取约会),但我不确定我是否正确获取了所有内容。计划如下:
- docker 容器:public 具有用于获取和设置约会的 REST 端点的交付应用程序。设置数据的端点正在触发 RabbitMQ 事件(异步),获取数据的端点正在调用命令服务(同步)。
- docker 容器:用于连接到 SQL 数据库以设置(和编辑)约会的命令服务。它正在监听主应用程序的 RabbidMQ 事件。更改不会覆盖数据,但会创建一个具有新版本的新条目。当数据发生变化时,它还会触发一个事件以将新数据同步到查询服务。
- docker 容器:命令服务的 SQL 数据库。
- docker 容器:连接到 MongoDB 的查询服务。它正在侦听命令服务中的更改以更新其数据库。主应用程序可以调用数据,但不能使用 REST,而是使用??
- docker 容器:一个事件源服务,用于侦听所有命令并将它们存储在 MongoDB.
中
- docker容器:事件MongoDB.
这里有几个我没有得到的问题:
假设命令数据库中有一个约会并且它已经同步到查询服务。现在有人呼吁更改此任命的名称。因此,命令服务不执行更新,而是执行具有相同 ID 但具有新版本号的插入。它后来在做什么?从 SQL 读取新数据并用它触发事件?查询服务正在侦听并将相同的数据存储在其 MongoDB?是覆盖旧数据还是创建一个带有版本的新条目?这似乎很多余?我真的需要这里的 SQL 数据库吗?
如果不想使用 REST,主应用如何从查询服务中调用数据?
因为它将所有命令存储在事件 DB(6.docker 容器)中,所以可以通过 运行 所有命令按顺序再次恢复每个状态。那是"event sourcing"吗?还是 "event sourcing" 不更改 SQL 中的数据,而是为每个更改创建一个新版本?我很困惑到底什么是事件溯源以及在哪里应用它。我真的需要 5.(和 6.)docker 事件源容器吗?
当客户端想要更改某些内容但随后也显示更改后的数据时,我看到的唯一方法是触发更改,而不是等待(比如说轮询)查询服务拥有它数据。实现这一目标的好方法是什么?也许检查未来版本号的存在?
整个结构是合理的架构还是我完全遗漏了什么?
抱歉,问题很多,但感谢您的帮助!
先看这个
Is this whole structure a reasonable architecture or am I completely
missing something?
漂亮的建筑计划!我知道感觉上有很多移动的部分,但是有很多小部分而不是一个大部分是我最喜欢的图案。
What is it doing afterwards? Reading the new data from the SQL and
triggering an event with it? The query service is listening and
storing the same data in its MongoDB? Is it overwriting the old data
or also creating a new entry with a version? That seems to be quite
redundant? Do I in fact really need the SQL database here?
CQRS 中有 2 个逻辑数据库(它们可以在同一个物理数据库中,但出于扩展的原因,最好不在同一物理数据库中)——域模型和读取模型。这些是非常不同的结构。域模型存储在任何具有第三范式等的 CRUD 应用程序中。读取模型旨在通过自定义设计匹配视图所需数据的 tables 来快速读取数据。这些table中会有大量的数据重复。这个想法是,每个视图都有一个 table 并在域模型更改时更新 table 会更灵敏,因为没有人坐在键盘前等待视图呈现,所以它可以视图模型数据生成需要更长的时间。这会导致一些 CPU 周期的浪费,因为您可以在任何人请求该视图之前多次更新视图模型,但这没关系,因为我们确实用完了空闲时间。
当命令更新聚合并将其持久保存到数据库时,它会为 CQRS 的视图端生成一条消息以更新视图。有两种方法可以做到这一点。第一种是发送一条消息说“聚合 83483 需要更新”,视图模型从域模型中重新查询它需要的所有内容并更新视图模型。另一种方法是发送一条消息说“聚合 83483 已更新为具有以下值:......”并且读取端可以更新其 tables 而无需查询。第一种方法需要较少的消息类型但需要更多的查询,而第二种方法则相反。您可以在同一个系统中混合和匹配这两种方法。
由于读取端具有非常不同的 table 结构,因此您需要两个数据库。在读取方面,除非您希望用户能够看到约会的旧版本,否则您只需存储视图的当前状态,以便仅更新现有数据。在命令端,使用版本号保持历史状态是个好主意,但会使数据库大小增加。
how can the main app call for data from the query service if one don't
want to uses REST?
请求如何到达查询端并不重要,因此您可以使用 REST、回发、GraphQL 或其他任何方式。
Is that "event sourcing"?
事件溯源是指您持久化对所有实体所做的所有更改。如果实体足够小,您可以保留所有属性,但通常事件只有更改。然后,为了获得当前状态,您将所有这些更改相加,以查看您的实体在某个时间点的样子。它与读取模型无关——那是 CQRS。请注意,事件不是用户做出更改的请求,而是用于创建命令的消息。事件是由于命令而更改的所有字段的记录。这是一个重要的区别,因为您不想在重新水合实体或聚合时重新运行所有业务逻辑。
When a client wants to change something but afterwards also show the
changed data the only way I see is to trigger the change and than wait
(let's say with polling) for the query service to have that data.
What's a good way to achieve that? Maybe checking for the existing of
the future version number?
显示历史数据有点粘。如果可以的话,我会拒绝这个要求,但有时这是必要的。如果必须这样做,请采用标准的读取模型方法并将所有更改保存到视图模型 table。如果情况合适,您可以直接从域模型 tables 中欺骗和读取历史数据,但这违反了 CQRS 规则。这很重要,因为 CQRS 的优势之一是它的可扩展性。如果每个读取实例维护自己的读取数据库,您可以根据需要扩展读取端,但必须从域模型读取会破坏它。这取决于具体情况,因此您必须自己决定,但最好的做法是尝试删除该要求。
就时间而言,CQRS 完全是关于最终一致性的。数据更改可能暂时不会显示在读取端(通常是几分之一秒,但这足以引起问题)。如果必须显示新旧数据,可以轮询并等待正确的版本号出现,这很丑陋。在 Rabbit 中还有其他涉及结果队列的替代方案,但它们甚至更丑陋。
我打算使用 CQRS 和事件溯源创建一个简单的微服务应用程序(设置和获取约会),但我不确定我是否正确获取了所有内容。计划如下:
- docker 容器:public 具有用于获取和设置约会的 REST 端点的交付应用程序。设置数据的端点正在触发 RabbitMQ 事件(异步),获取数据的端点正在调用命令服务(同步)。
- docker 容器:用于连接到 SQL 数据库以设置(和编辑)约会的命令服务。它正在监听主应用程序的 RabbidMQ 事件。更改不会覆盖数据,但会创建一个具有新版本的新条目。当数据发生变化时,它还会触发一个事件以将新数据同步到查询服务。
- docker 容器:命令服务的 SQL 数据库。
- docker 容器:连接到 MongoDB 的查询服务。它正在侦听命令服务中的更改以更新其数据库。主应用程序可以调用数据,但不能使用 REST,而是使用??
- docker 容器:一个事件源服务,用于侦听所有命令并将它们存储在 MongoDB. 中
- docker容器:事件MongoDB.
这里有几个我没有得到的问题:
假设命令数据库中有一个约会并且它已经同步到查询服务。现在有人呼吁更改此任命的名称。因此,命令服务不执行更新,而是执行具有相同 ID 但具有新版本号的插入。它后来在做什么?从 SQL 读取新数据并用它触发事件?查询服务正在侦听并将相同的数据存储在其 MongoDB?是覆盖旧数据还是创建一个带有版本的新条目?这似乎很多余?我真的需要这里的 SQL 数据库吗?
如果不想使用 REST,主应用如何从查询服务中调用数据?
因为它将所有命令存储在事件 DB(6.docker 容器)中,所以可以通过 运行 所有命令按顺序再次恢复每个状态。那是"event sourcing"吗?还是 "event sourcing" 不更改 SQL 中的数据,而是为每个更改创建一个新版本?我很困惑到底什么是事件溯源以及在哪里应用它。我真的需要 5.(和 6.)docker 事件源容器吗?
当客户端想要更改某些内容但随后也显示更改后的数据时,我看到的唯一方法是触发更改,而不是等待(比如说轮询)查询服务拥有它数据。实现这一目标的好方法是什么?也许检查未来版本号的存在?
整个结构是合理的架构还是我完全遗漏了什么?
抱歉,问题很多,但感谢您的帮助!
先看这个
Is this whole structure a reasonable architecture or am I completely missing something?
漂亮的建筑计划!我知道感觉上有很多移动的部分,但是有很多小部分而不是一个大部分是我最喜欢的图案。
What is it doing afterwards? Reading the new data from the SQL and triggering an event with it? The query service is listening and storing the same data in its MongoDB? Is it overwriting the old data or also creating a new entry with a version? That seems to be quite redundant? Do I in fact really need the SQL database here?
CQRS 中有 2 个逻辑数据库(它们可以在同一个物理数据库中,但出于扩展的原因,最好不在同一物理数据库中)——域模型和读取模型。这些是非常不同的结构。域模型存储在任何具有第三范式等的 CRUD 应用程序中。读取模型旨在通过自定义设计匹配视图所需数据的 tables 来快速读取数据。这些table中会有大量的数据重复。这个想法是,每个视图都有一个 table 并在域模型更改时更新 table 会更灵敏,因为没有人坐在键盘前等待视图呈现,所以它可以视图模型数据生成需要更长的时间。这会导致一些 CPU 周期的浪费,因为您可以在任何人请求该视图之前多次更新视图模型,但这没关系,因为我们确实用完了空闲时间。
当命令更新聚合并将其持久保存到数据库时,它会为 CQRS 的视图端生成一条消息以更新视图。有两种方法可以做到这一点。第一种是发送一条消息说“聚合 83483 需要更新”,视图模型从域模型中重新查询它需要的所有内容并更新视图模型。另一种方法是发送一条消息说“聚合 83483 已更新为具有以下值:......”并且读取端可以更新其 tables 而无需查询。第一种方法需要较少的消息类型但需要更多的查询,而第二种方法则相反。您可以在同一个系统中混合和匹配这两种方法。
由于读取端具有非常不同的 table 结构,因此您需要两个数据库。在读取方面,除非您希望用户能够看到约会的旧版本,否则您只需存储视图的当前状态,以便仅更新现有数据。在命令端,使用版本号保持历史状态是个好主意,但会使数据库大小增加。
how can the main app call for data from the query service if one don't want to uses REST?
请求如何到达查询端并不重要,因此您可以使用 REST、回发、GraphQL 或其他任何方式。
Is that "event sourcing"?
事件溯源是指您持久化对所有实体所做的所有更改。如果实体足够小,您可以保留所有属性,但通常事件只有更改。然后,为了获得当前状态,您将所有这些更改相加,以查看您的实体在某个时间点的样子。它与读取模型无关——那是 CQRS。请注意,事件不是用户做出更改的请求,而是用于创建命令的消息。事件是由于命令而更改的所有字段的记录。这是一个重要的区别,因为您不想在重新水合实体或聚合时重新运行所有业务逻辑。
When a client wants to change something but afterwards also show the changed data the only way I see is to trigger the change and than wait (let's say with polling) for the query service to have that data. What's a good way to achieve that? Maybe checking for the existing of the future version number?
显示历史数据有点粘。如果可以的话,我会拒绝这个要求,但有时这是必要的。如果必须这样做,请采用标准的读取模型方法并将所有更改保存到视图模型 table。如果情况合适,您可以直接从域模型 tables 中欺骗和读取历史数据,但这违反了 CQRS 规则。这很重要,因为 CQRS 的优势之一是它的可扩展性。如果每个读取实例维护自己的读取数据库,您可以根据需要扩展读取端,但必须从域模型读取会破坏它。这取决于具体情况,因此您必须自己决定,但最好的做法是尝试删除该要求。
就时间而言,CQRS 完全是关于最终一致性的。数据更改可能暂时不会显示在读取端(通常是几分之一秒,但这足以引起问题)。如果必须显示新旧数据,可以轮询并等待正确的版本号出现,这很丑陋。在 Rabbit 中还有其他涉及结果队列的替代方案,但它们甚至更丑陋。