具有CQRS和事件来源的示例micoservice应用

时间:2018-07-17 11:11:25

标签: microservices cqrs event-sourcing

我正计划使用CQRS和事件源创建一个简单的微服务应用程序(设置并获取约会),但是我不确定是否一切正确。这是计划:

  1. docker容器:具有REST端点的公共交付应用程序,用于获取和设置约会。设置数据的端点正在触发RabbitMQ事件(异步),获取数据的端点正在调用命令服务(同步)。
  2. docker容器:用于与SQL数据库连接以设置(和编辑)约会的命令服务。它正在监听主应用程序的RabbidMQ事件。更改不会覆盖数据,而是创建具有新版本的新条目。数据更改后,还会触发事件以将新数据同步到查询服务。
  3. docker容器:命令服务的SQL数据库。
  4. docker容器:与MongoDB连接的查询服务。它正在侦听命令服务中的更改以更新其数据库。主应用程序可以调用数据,但不能使用REST而是使用??
  5. docker容器:一种事件源服务,用于侦听所有命令并将其存储在MongoDB中。
  6. docker容器:事件MongoDB。

以下是我没有得到的几个问题:

  • 让我们说命令数据库中有一个约会,并且它已经同步到查询服务。现在有一个呼吁更改此约会的标题。因此,命令服务不会执行UPDATE,而是执行具有相同ID但具有新版本号的INSERT。后来做什么?从SQL读取新数据并以此触发事件?查询服务正在监听相同的数据并将其存储在其MongoDB中?是覆盖旧数据还是用版本创建新条目?那似乎很多余吗?实际上,我真的需要这里的SQL数据库吗?

  • 如果不想使用REST,主应用程序如何从查询服务中调用数据?

  • 由于它将所有命令存储在事件数据库(6. docker容器)中,因此可以通过再次依次运行所有命令来恢复每个状态。那是“事件来源”吗?还是“事件源”是不更改SQL中的数据而是为每次更改创建一个新版本?我很困惑确切的事件源是什么以及在哪里应用。我真的需要5.(和6.)泊坞窗容器进行事件采购吗?

  • 当客户想要更改某些内容,但之后还要显示更改后的数据时,我看到的唯一方法是触发更改,然后等待(比如说轮询)查询服务拥有该数据。有什么好的方法可以做到这一点?也许要检查将来版本号的存在性?

  • 整个结构是合理的体系结构还是我完全缺少什么?

对不起,很多问题,但是谢谢您的帮助!

1 个答案:

答案 0 :(得分:2)

让我们先来看这个。

  

整个结构是合理的架构还是我完全是   缺少什么?

好的建筑计划!我知道感觉上有很多移动的棋子,但是使很多小棋子而不是一个大棋子成为了我最喜欢的模式。

  

此后做什么?从SQL读取新数据并   触发事件吗?查询服务正在监听   在其MongoDB中存储相同的数据?是否覆盖旧数据   还是用版本创建新条目?那似乎相当   多余的?实际上我真的需要这里的SQL数据库吗?

CQRS中有2个逻辑数据库(可以在同一物理数据库中,但是出于扩展的原因,最好是不在同一数据库中)–域模型和读取模型。这些是非常不同的结构。域模型的存储方式与任何CRUD应用程序一样,具有第三种标准形式,等等。读取模型的目的是通过与视图所需数据匹配的定制设计表来快速读取数据。这些表中将有很多数据重复。这个想法是,为每个视图创建一个表并在域模型更改时更新该表的响应速度更快,因为没有人坐在键盘旁等待视图渲染,因此视图模型数据生成可以花一点时间更长。这会导致CPU周期的浪费,因为您可以在任何人请求该视图之前更新视图模型几次,但这没关系,因为无论如何我们实际上都在浪费空闲时间。

当命令更新聚合并将其持久保存到DB时,它将为CQRS的视图端生成一条消息以更新视图。有两种方法可以做到这一点。第一种是发送一条消息,说“聚合83483需要更新”,并且视图模型从域模型中查询其所需的一切并更新视图模型。另一种方法是发送一条消息,说“聚合83483已更新为具有以下值:...”,而读取端可以更新其表而无需查询。第一种方法需要较少的消息类型,但需要更多的查询,而第二种方法则相反。您可以在同一系统中混合使用这两种方法。

由于读取端具有非常不同的表结构,因此您需要两个数据库。在读取方面,除非您希望用户能够看到约会的旧版本,否则只需存储视图的当前状态即可,只需更新现有数据即可。在命令方面,使用版本号保持历史状态是一个好主意,但可以使数据库大小增加。

  

主应用程序如何从查询服务中调用数据?   想使用REST?

请求到达查询端的方式并不重要,因此您可以使用REST,回发,GraphQL或其他任何方式。

  

是“事件来源”吗?

事件来源是当您保留对所有实体所做的所有更改时。如果实体足够小,则可以保留所有属性,但通常情况下只有更改。然后,要获取当前状态,请将所有这些更改加起来,以查看实体在特定时间点的外观。它与读取模型无关-CQRS。请注意,事件不是用户要求进行更改的请求,而是一条消息,然后将其用于创建命令。事件是由于命令而更改的所有字段的记录。这是一个重要的区别,因为您不希望在对实体或集合进行水化处理时重新运行所有业务逻辑。

  

如果客户想要更改某些内容,但之后还要显示   更改数据,我看到的唯一方法是触发更改,然后等待   (比方说轮询)让查询服务拥有该数据。   有什么好的方法可以做到这一点?也许检查是否存在   将来的版本号?

显示历史数据有点棘手。如果可以的话,我会尽量取消这一要求,但有时是有必要的。如果必须这样做,则采用标准的读取模型方法,并将所有更改保存到视图模型表中。如果情况合适,您可以直接从域模型表中作弊并读取历史数据,但这违反了CQRS规则。这很重要,因为CQRS的优点之一是它的可伸缩性。如果每个读取实例都维护自己的读取数据库,则可以根据需要扩展读取端的大小,但是必须从域模型读取会破坏这一点。这取决于情况,因此您必须自行决定,但是最好的做法是尝试删除该要求。

就时间安排而言,CQRS的关键在于最终的一致性。数据更改可能不会在读取端显示一段时间(通常是几分之一秒,但这足以引起问题)。如果必须显示新数据和旧数据,则可以轮询并等待正确的版本号出现,这很丑陋。 Rabbit中还有其他涉及结果队列的替代方案,但它们甚至更难看。