[云原生微服务架构](五)CQRS

CQRS 是一种与领域驱动设计 (DDD) 和事件溯源相关的架构模式。Greg Young 在 2010 年创造了这个术语,CQRS 的内容基于 Bertrand Meyer 的 CQS 设计模式。但它的背后是什么?

CQS (命令查询分离)设计模式建议将对象的方法映射到两类:方法要么改变对象的内部状态,但不返回任何内容,要么只返回元数据。这种方法称为Command。或者一个方法返回信息但不改变内部状态。这种方法称为Query

根据 CQS,一个方法永远不应该同时存在。比如看栈的典型数据结构,push函数是一个命令,而top是一个查询。最后,pop 函数违反了 CQS 模式,因为它修改了堆栈的内部状态并同时返回信息。

因此,CQS 的核心是在单个对象上分离写入和读取。这尤其重要,例如,当代码要并行执行时:由于没有副作用,查询可以并行化而没有任何问题,但命令不能。

在 CQS 模式的基础上,Greg Young 在 2010 年创造了CQRS(Command Query Responsibility Segregation)架构模式。它也将写入和读取分开,但在 API 方面。因此,它提出了单独的 API,一个专用于更改应用程序状态的命令路由,另一个专用于返回有关应用程序状态信息的查询路由。

从技术上讲,这可以在 HTTP 中实现,因此 Command API 仅使用 POST 路由实现,而 Query API 仅使用 GET 路由实现。实际语义被转移到 URL。例如,下订单需要向/submit-order路由发送 POST 请求。这与 REST 形成对比,后者仅允许 HTTP 动词作为动词,因此技术语义已经在 API 级别丢失。

如果进一步把 CQRS 的思路拿来,把 API 后面的数据库分成两个数据库也是有道理的。一个应该针对写入进行优化,另一个针对读取进行优化——例如,通过对一个进行大量规范化,同时对另一个进行非规范化。一路走来,在写入时可以确保良好的完整性和一致性,同时在读取时实现高效率和高性能。

使用 CQRS 开发 API

如果要将 CQRS 模式实现到 API 中,仅通过 POST 和 GET 分隔路由是不够的。您还必须考虑如何确保该命令不返回任何内容,或者至少只返回元数据。

情况与查询 API 类似。这里,URL 路径描述了所需的查询,但在这种情况下,参数是使用查询字符串传输的,因为它是一个 GET 请求。由于查询访问读取优化的非规范化数据库,因此可以快速有效地执行查询。

但是,问题在于,如果不定期拉取查询路由,客户端就不会知道某个命令是否已经被处理过以及结果是什么。因此,建议使用第三种 API,即事件 API,它通过 Web 套接字、HTTP 流或类似机制的推送通知来通知事件。

任何了解 GraphQL 并在描述命令、查询和事件 API 时想起突变、查询和订阅概念的人都在正确的轨道上:GraphQL 是实现基于 CQRS 的 API 的理想选择。

CQRS、DDD 和事件溯源

正如最初提到的,CQRS 经常与领域驱动设计(DDD) 和Event-Sourcing结合使用。这三个概念虽然是独立的,但它们相得益彰!

发送到基于 CQRS 的应用程序的命令 API 的命令也可以在 DDD 意义上解释为聚合的命令。然后聚合按顺序生成一个或多个域事件,这些事件可以使用事件溯源**存储在事件存储**中,并用于聚合的稍后重放。

此外,流程中生成的领域事件也被转发到事件 API,事件 API 又将它们传递给各个连接的客户端,这些客户端接收有关应用程序内技术流程的准实时更新。

此外,领域事件也被转发到应用程序的读取页面以更新那里的(预先计算的)视图。为此,使用了所谓的投影,它决定技术领域事件与哪个视图有什么相关性,然后在 CRUD 语句的帮助下相应地调整受影响的视图。

在这种情况下,事件存储代表了单一的事实来源,之后可以借助它来设置任何视图。但是,应该注意的是,写入视图与写入事件存储是分离的,因此开发人员应该熟悉 CAP 定理和最终一致性。

CAP 和最终一致性

原则上,CAP 定理描述了一个三角形,其角代表ConsistencyAvailabilityPartition Tolerance。如果在向客户端确认之前首先将写访问复制到所有其他应用程序节点,则分布式应用程序的行为是一致的(一致性)。分布式系统的所有节点总是可以提供统一的响应。

另一方面,可用性方面描述了分布式系统可以随时对读写请求做出反应——由于系统的状态,永远不会有任何等待时间或拒绝请求。最后,分区容错要求分布式应用程序继续运行,即使单个节点发生故障或它们之间的网络连接中断。

CAP 定理现在指出,这三个方面中只有两个是可能的。特别是,这意味着只有当您可以排除任何硬件故障时,一个始终一致且可用的系统 (CA) 才是可能的,而这实际上是不可能的。因此,在实践中,您只能在 CP 和 AP 之间进行选择,即分布式系统是否应该在服务器或网络发生故障时放弃可用性或一致性。

缺乏一致性一开始听起来很危险,但这是“不一致性”,而是一种“延迟一致性”。最后,一旦故障服务器再次可用,一致性就会恢复。这正是术语“最终一致性”的含义,在德语中最好等同于“最终一致”或“最终一致”。

只要您不是为高度安全的关键领域(公共基础设施、医疗系统……)开发应用程序,最终的一致性就不成问题。放弃始终保证的强一致性会导致应用程序更易于访问和响应,这可能是一个巨大的(业务)优势,尤其是在 Web 和云环境中。还应该记住,现实只在极少数情况下表现出绝对的一致性。

为什么选择 CQRS?

最后,问题仍然是为什么要处理 CQRS。一个明显的原因是它对 DDD、Event-Sourcing 和 GraphQL 等其他几个概念的明显补充。此外,CQRS 从一开始就将读写分离,针对分布式架构,使其非常适合用于运行在 Web 或云上的基于服务的系统。

因此,CQRS 还提供了微服务架构的所有优势,例如各个服务的可扩展性、可维护性和可测试性。使用 CQRS 也可以轻松操作不同版本的业务逻辑,并且可以具体限制各个服务的访问权限,从而有利于整个系统的安全性。

然而,缺点在于其架构本质上比传统的客户端-服务器系统更复杂——但应该记住,CQRS 也提供了几个优点,但这些都是有代价的。

CQRS 的最大优势之一是可以将技术代码与业务代码分离,特别是在与 DDD 和事件溯源相关的情况下。因此,可以在不更改技术子结构中的任何内容的情况下调整业务逻辑。同理,更重要的是反之亦然,有利于业务逻辑的长期稳定性和绝对信心。

由于提到的复杂性,建议考虑是否在已经实现 CQRS 和 Event-Sourcing 的合适框架上构建您的应用程序,以便作为开发人员,您可以主要专注于设计和编写技术代码。此类框架可用于多种技术、语言和平台,例如 Spring(Java)或 NestJS(TypeScript、NodeJS)。

结论

CQRS 是一种令人兴奋的分布式架构方法,可以发挥其优势,尤其是在 DDD 和事件溯源方面。尽管复杂性超过了经典的客户端-服务器架构,但您还可以获得一个更具可扩展性的应用程序,该应用程序始终可以更好地映射基本功能。

本作品采用《CC 协议》,转载必须注明作者和本文链接
你还差得远呐!
讨论数量: 0
(= ̄ω ̄=)··· 暂无内容!

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!