什么是CQRS,为什么它越来越受欢迎?
CQRS是一种与领域驱动设计(DDD)和事件溯源相关的架构模式。GregYoung在年创造了这个术语,CQRS的内容基于BertrandMeyer的CQS设计模式。但它的背后是什么?
CQS(命令查询分离)设计模式建议将对象的方法映射到两类:方法要么改变对象的内部状态,但不返回任何内容,要么只返回元数据。这种方法称为Command。或者一个方法返回信息但不改变内部状态。这种方法称为Query。
根据CQS,一个方法永远不应该同时存在。比如看栈的典型数据结构,push函数是一个命令,而top是一个查询。最后,pop函数违反了CQS模式,因为它修改了堆栈的内部状态并同时返回信息。
因此,CQS的核心是在单个对象上分离写入和读取。这尤其重要,例如,当代码要并行执行时:由于没有副作用,查询可以并行化而没有任何问题,但命令不能。
在CQS模式的基础上,GregYoung在年创造了CQRS(CommandQueryResponsibilitySegregation)架构模式。它也将写入和读取分开,但在API方面。因此,它提出了单独的API,一个专用于更改应用程序状态的命令路由,另一个专用于返回有关应用程序状态信息的查询路由。
从技术上讲,这可以在HTTP中实现,因此CommandAPI仅使用POST路由实现,而QueryAPI仅使用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定理描述了一个三角形,其角代表Consistency、Availability和PartitionTolerance。如果在向客户端确认之前首先将写访问复制到所有其他应用程序节点,则分布式应用程序的行为是一致的(一致性)。分布式系统的所有节点总是可以提供统一的响应。
另一方面,可用性方面描述了分布式系统可以随时对读写请求做出反应——由于系统的状态,永远不会有任何等待时间或拒绝请求。最后,分区容错要求分布式应用程序继续运行,即使单个节点发生故障或它们之间的网络连接中断。
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和事件溯源方面。尽管复杂性超过了经典的客户端-服务器架构,但您还可以获得一个更具可扩展性的应用程序,该应用程序始终可以更好地映射基本功能。
感谢您阅读我关于CQRS的文章。我希望你能从中拿走一些东西。
干杯!