不同类型的CQRS架构之间的对比及应用:选择适合自己的CQRS(译)

1-Image by author

Command Query Responsibility Segregation (CQRS) is a vast ocean of deep topics within The World of Software Architecture. It is often stigmatised with huge complexity, and many engineers are reluctant to dip their toes in the water.

Some great articles talk through complex, eventually consistent, distributed CQRS system architectures that can handle massive scale. If you are just getting started with CQRS, then this can be a little daunting. In reality, there are also much simpler options that work well for most problems.

命令查询责任隔离(CQRS)是软件架构世界中的一个深奥课题。它通常被认为非常复杂,许多工程师都不愿涉足其中。

一些精彩的文章介绍了可处理大规模问题的复杂、最终一致的分布式 CQRS 系统架构。如果您刚刚开始接触 CQRS,那么这可能会让您有些望而生畏。实际上,也有一些简单得多的方案可以很好地解决大多数问题。

Command Query Responsibility Segregation (CQRS)

命令查询责任隔离 (CQRS)

CQRS splits data access into Commands and Queries.

CQRS 将数据访问分为命令和查询。

  • Commands: Write data – Create/Update/Delete

  • Queries: Read data

  • 命令:写入数据 - 创建/更新/删除

  • 查询:读取数据

2-CQRS Components

Each Command and Query class has a corresponding Handler class. Generally, Commands and Queries are dispatched to their Handler using a synchronous in-process Mediator. Sometimes asynchronous methods, such as a Message Bus, are used for handling Commands when there are high-scale requirements.

Splitting Write and Read operations means we can optimise each side independently. This might mean different Write and Read models. It might even mean completely different databases. That choice depends on the non-functional requirements of your app.

每个命令和查询类都有一个相应的处理程序类。一般情况下,命令和查询使用同步进程内调解器分派给处理程序。当有大规模需求时,有时会使用异步方法(如消息总线)来处理命令。

拆分写入和读取操作意味着我们可以独立优化每一方。这可能意味着不同的写入和读取模型。甚至可能意味着完全不同的数据库。这种选择取决于应用程序的非功能性要求。

Let’s talk through some of the options and when they can be used.

让我们来谈谈其中的一些选项以及何时可以使用它们。

Single Read/Write Model, Single Database

单一读/写模型,单一数据库

This is the simplest flavour of CQRS, where our Commands and Queries use the same Model/Entity classes. For most small-to-medium-sized apps, this is generally fine!

这是最简单的 CQRS,我们的命令和查询使用相同的模型/实体类。对于大多数中小型应用程序来说,这通常是没有问题的!

3-Single Read/Write Model, Single Database

  • Consistency: Strong

  • Complexity: Low

  • Performance/Scalability: Low

  • 一致性:强

  • 复杂性:低

  • 性能/可缩放性:低

This is a great option if you are new to using CQRS; it still provides one of the biggest benefits that CQRS brings: clean code and separation of concerns. Splitting our code into granular Commands/Queries/Handlers ensures that the Single Responsibility Principle (SRP) is adhered to, which makes our solutions flexible for change and easy to test.

如果您是使用 CQRS 的新手,这是一个很好的选择;它仍然提供了 CQRS 带来的最大好处之一:代码简洁和关注点分离。将我们的代码拆分为细粒度的命令/查询/处理程序,可确保遵守单一责任原则 (SRP),从而使我们的解决方案能够灵活地应对变化并易于测试。

Different Read/Write Models, Single Database

不同的读/写模型,单一数据库

Using different Read and Write Models allows us to optimise each side slightly differently, generally for performance. Our options on either side are fairly limited since we use the same database for Commands and Queries.

使用不同的读取和写入模型可以让我们以略微不同的方式优化每一方,通常是为了提高性能。由于命令和查询使用的是同一个数据库,因此两边的选择都相当有限。

4-Different Read/Write Models, Single Database

  • Consistency: Strong

  • Complexity: Low/Medium

  • Performance/Scalability: Medium

  • 一致性:强

  • 复杂性:低/中

  • 性能/可缩放性:中

We have a few options on each side now. We could use a heavier ORM for writing data and something lightweight for querying data. Here, we use different classes to represent our Write/Read sides, and we could even use completely different database tables or views if we like.

Generally, something more involved, like Domain-Driven Design, would be used on the Write side, and much simpler DTOs with no business logic would be used on the Read side. The Read models should be optimised for faster serialization and querying, so there should be little or no mapping being performed.

Since we still use a single database, we can commit Write and Read model changes in a single atomic transaction to ensure consistency. This style still keeps things simple but lets us optimise our Queries slightly better.

现在我们在两方面都有一些选择。我们可以使用重型 ORM 来写入数据,使用轻量级的 ORM 来查询数据。在这里,我们使用不同的类来表示写入/读取两侧,如果愿意,我们甚至可以使用完全不同的数据库表或视图。

一般来说,写入端会使用更复杂的东西(如领域驱动设计),而读取端会使用简单得多的无业务逻辑的 DTO。读取模型应为更快的序列化和查询而优化,因此应很少或根本不需要执行映射。

由于我们仍然使用单个数据库,因此可以在单个原子事务中提交写入和读取模型的更改,以确保一致性。这种风格仍然保持简单,但能让我们更好地优化查询。

Different Read/Write Databases

不同的读/写数据库

This is where things get really interesting, and a lot more complicated! This setup is also what people generally think of when they talk about CQRS.

Using different databases for Read and Write means we can use a Polyglot Architecture, where we pick a database that perfectly fits the problem on each side. The choice of databases will completely depend on your team and app requirements.

You might want to use something simple and cheap like S3 Buckets for the Write side and something with better query support on the Read side, such as Elastic Search. A relational SQL database may fit better on one side and a NoSQL database on the other. Depending on data access patterns, we can also scale each side completely independently.

这就是事情变得有趣和复杂的地方!人们在谈论 CQRS 时,通常也会想到这种设置。

使用不同的数据库进行读取和写入意味着我们可以使用多语言架构,即我们可以选择一个完全适合每一方问题的数据库。数据库的选择完全取决于您的团队和应用需求。

您可能希望在写入端使用 S3 Buckets 等简单便宜的工具,而在读取端使用 Elastic Search 等查询支持更好的工具。关系型 SQL 数据库可能更适合放在一侧(写入端),而 NoSQL 数据库则放在另一侧(读取端)。根据数据访问模式,我们还可以完全独立地扩展每一侧。

5-Different Read/Write Databases

  • Consistency: Eventual Consistency

  • Complexity: High

  • Performance/Scalability: High

  • 一致性:最终一致性

  • 复杂性:高

  • 性能/可缩放性:高

Whilst this may seem like The Holy Grail of Architectures, the price we pay is huge complexity and weak consistency. Since different databases are being used, we cannot commit changes to our Write and Read models in a single atomic transaction. Generally, changes to the Write models are propagated to the Read Models using asynchronous messaging/events, providing Eventual Consistency.

We must contend with problems like: What happens if events propagate out of order? What if we lose events? What if our Read Models become out of sync? What if saving the Read Model fails? How does the UI know when Read Models have been updated after a write so they can be queried?

This style of CQRS is compelling but extremely complex to build and manage. Battling with the CAP Theorem and managing distributed transactions is one of the hardest problems in software engineering! This option should only be chosen if the non-functional requirements of your app require it.

虽然这看起来像是架构的圣杯,但我们付出的代价却是巨大的复杂性和较弱的一致性。由于使用的是不同的数据库,我们无法在单个原子事务中提交对写入和读取模型的更改。一般来说,对写入模型的更改会通过异步消息/事件传播到读取模型,从而提供最终一致性。

我们必须解决以下问题 如果事件传播顺序错乱怎么办?如果我们丢失了事件怎么办?如果我们的读取模型不同步怎么办?如果保存读取模型失败怎么办?用户界面如何知道读取模型在写入后何时更新,以便进行查询?

这种风格的 CQRS 虽然引人注目,但构建和管理起来却极其复杂。与 CAP 定理作斗争和管理分布式事务是软件工程中最难解决的问题之一!只有在应用程序的非功能性要求需要时,才应选择此选项。

Event Sourcing — Different Read/Write Databases

事件源–不同的读/写数据库

It can be challenging to keep everything in sync when using separate Read/Write databases and Eventual Consistency. The order of events published from the Write to the Read side becomes really important.

Imagine that the same Write Model instance is updated twice in close succession. If the first update event is delivered after the second event, our Read model may be updated with stale data.

Unfortunately, most asynchronous Message Buses are built to be highly available and performant — this means they do not guarantee that messages will be delivered in the same order they are published. Event Sourcing can help us with this problem by taking a completely different approach to storing our Write Models.

在使用独立的读/写数据库和最终一致性时,保持所有内容同步是一项挑战。事件从写入端发布到读取端的顺序变得非常重要。

试想一下,同一个写模型实例在很短的时间内连续更新了两次。如果第一个更新事件在第二个事件之后发布,那么我们的读取模型可能会被更新为陈旧的数据。

遗憾的是,大多数异步消息总线都是为实现高可用性和高性能而构建的,这意味着它们不能保证消息以与发布顺序相同的顺序交付。通过采用完全不同的方法来存储写入模型,事件源可以帮助我们解决这个问题。

6-Event Sourcing — Different Read/Write Databases

  • Consistency: Eventual Consistency

  • Complexity: High

  • Performance/Scalability: High

  • 一致性:最终一致性

  • 复杂性:高

  • 性能/可缩放性:高

Instead of storing the current state of a model, append-only event stores are used to record the full series of actions taken on a model. When a new Command occurs, the current state of the Model/Entity is ‘rehydrated’ by replaying all of the events that have ever happened for that instance.

Each model instance on the Write side is stored as its own independent ‘Event Stream.’ The stream of events can be replayed at any time to materialise different views of the data. If the Read side gets out of sync, we can query all of the events from the Write side and rebuild our models.

As well as helping manage the consistency problem, Event Sourcing also provides some other benefits. We don’t need to implement complex audit processes anymore since our Event Streams already contain everything that has ever happened to each Model/Entity instance. If we decide that additional Read Models are needed in the future, we can replay the events to generate them.

Event Sourcing provides a really powerful and flexible way to model your data, but it is, again, even more complex to take on. If you have never used CQRS, Event Sourcing, or distributed architectures, then starting here is very ambitious.

只附加事件存储用于记录模型上发生的所有操作,而不是存储模型的当前状态。当一个新命令发生时,模型/实体的当前状态会通过重放该实例发生过的所有事件而被 “重新水化”。

除了帮助管理一致性问题,事件源还提供了其他一些好处。我们不再需要实施复杂的审计流程,因为我们的事件流已经包含了每个模型/实体实例所发生的一切。如果我们决定将来需要额外的读取模型,我们可以重放事

事件源为数据建模提供了一种非常强大和灵活的方法,但它同样也是一种更加复杂的方法。如果您从未使用过 CQRS、事件源或分布式架构,那么从这里开始是非常有挑战性的。

Event Sourcing — Single Database

活动采购–单一数据库

If you want to leverage the benefits of CQRS and Event Sourcing but don’t have huge scale requirements, then this can be a great place to start! Using the same database to store your Event Streams and materialised Read Models means we can eliminate all our consistency woes by committing both in a single transaction.

如果您想利用 CQRS 和事件源的优势,但又没有巨大的规模需求,那么这将是一个很好的开始!使用相同的数据库来存储事件流和具体化读取模型,意味着我们可以通过在单个事务中提交两者来消除所有一致性问题。

7-Event Sourcing — Single Database

  • Consistency: Strong

  • Complexity: Medium

  • Performance/Scalability: Medium

  • 一致性: 强

  • 复杂度: 中等

  • 性能/可缩放性:中等

If you are using a schemaless NoSQL database, then storing the Event Streams and Read Models is easy. If you are using a relational database, you can store your Event Streams as text in a JSON format.

No matter what type of problem you are solving, there is a flavour of CQRS that can work well for you. The non-functional requirements of your system should drive the decision on which to use. Start simple and change if your scale requires it. As with most software problems, it is best not to optimise too early.

如果使用的是无模式 NoSQL 数据库,那么存储事件流和读取模型就很容易。如果使用的是关系数据库,则可以 JSON 格式的文本存储事件流。

无论您要解决什么类型的问题,总有一种 CQRS 适合您。应根据系统的非功能性要求来决定使用哪种系统。先从简单的开始,如果规模需要,再进行更改。与大多数软件问题一样,最好不要过早进行优化。

【参考文献】

文章:Choosing a CQRS Architecture That Works for You

作者:Matt Bentley

日期:2023年10月6号

上述译文仅供参考,原文请查看下面链接,解释权归原作者所有

⚠️:文章翻译如有语法不准确或者内容纰漏,欢迎评论区指正。

【关于TalkX】

TalkX是一款基于GPT实现的IDE智能开发插件,专注于编程领域,是开发者在日常编码中提高编码效率及质量的辅助工具,TalkX常用的功能包括但不限于:解释代码、中英翻译、性能检查、安全检查、样式检查、优化并改进、提高可读性、清理代码、生成测试用例等。

TalkX建立了全球加速网络,不需要考虑网络环境,响应速度快,界面效果和交互体验更流畅。并为用户提供了OpenAI的密钥,不需要ApiKey,不需要自备账号,不需要魔法。

TalkX产品支持:JetBrains (包括 IntelliJ IDEA、PyCharm、WebStorm、Android Studio)、HBuilder、VS Code、Goland.

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值