当前位置: 首页 > 科技观察

如何写出简洁的CQRS代码?

时间:2023-03-15 20:27:05 科技观察

命令和查询责任分离(CQRS)是指一种模式,将数据存储的读取和更新操作分开。据说实施CQRS可以提高性能、可伸缩性和安全性。通过迁移到CQRS模式创建的灵活性允许系统随着时间的推移更好地发展。但是,CQRS模式有一些众所周知的缺陷,本文介绍了三个实际场景。CQRS模式可以创造奇迹:它可以最大限度地提高可扩展性、性能、安全性,甚至可以打破CAP定理(1)。尽管如此,CQRS因其引入的复杂性而获得了一个有争议的名称。例如,MartinFowler在他的CQRS文章(2)中认为应该谨慎甚至谨慎地使用该模式。对于大多数系统,CQRS增加了风险复杂性。您应该非常小心地使用CQRS。虽然CQRS是工具箱中的一种模式,但要知道它很难用好。很容易搞砸您的重要组件。在我看来,CQRS引入的复杂性在很大程度上是偶然的并且是可以避免的。为了说明我的观点,我想讨论CQRS的目标,然后分析基于CQRS的系统中常见的三个复杂性来源。CQRS的目标CQRS的目标是使用多个模型来表示相同的数据,而不考虑可扩展性、可用性、安全性、性能。在多个模型中表示相同的数据,这是目标,其余的都是副产品。不相信吗?听听GregYoung在DDDEU2016大会上的演讲(3),他说发明CQRS是为了支持EventSourcing(事件追踪)的实现。而且大家可能都知道,EventSourcing模型写数据的时候很厉害,但是读数据的时候就惨了。这也是他当年需要CQRS的原因:用多个模型来表示同一个数据。CQRS是如何实现这个目标的?通过确保只有一个模型作为数据源,所有的修改都通过这个模型进行。让我们看看这种解释如何帮助我们解决一些复杂的问题。复杂性陷阱一:单向顺序,或过度隔离据我所知,所有CQRS的定义都遵循这种模式。CQRS基于CQS原则,该原则规定操作应分为两组:更改数据的命令和查询数据的命令。一旦我们将这个原则提升到架构级别,我们最终会得到一个系统,其中用例被分为相同的两组:命令和查询。每个用例可以是命令或查询,但不能同时是两者。一旦用例被隔离,我们得到了很多好处:多种模型、不同的持久化机制、独立的可扩展性等等。你觉得这里有问题吗?问题很微妙:所有的CQRS定义通常都是从解决方案开始——隔离,然后定义问题——多模型。这导致对隔离过于热衷:即使将命令定义为单向,操作服务器也只是返回一个Ack/Nack响应,并且必须轮询一些读取模型存储以获取实际命令以返回结果。换句话说,复杂性被释放得一塌糊涂。解决方案:放松隔离让我们退后一步,重新考虑隔离问题。我们已经看到,根据CQRS,为了在多个模型中表示相同的数据,一个用例可以同时写入数据和读取数据。不言而喻,读模型不应该更新任何东西,否则我们最终会得到多个数据源。但是,你真的应该让你的命令空转吗?不会,一个命令可以安全的返回下面的数据,不违背任何原则。执行结果:成功/失败。如果失败:错误消息或验证错误。如果成功:聚合的新版本号。此信息将极大地改善您系统的用户体验,因为:您无需查询外部资源以获取命令执行结果,您可以立即获得。验证命令和返回错误消息变得非常容易;如果要刷新显示的数据,可以使用新版本的聚合来确定视图模型是否反映了执行的命令。过时的数据将不再显示。说到数据,我们能不能放松一点隔离?在许多情况下,受影响聚合中包含的任何数据都可以作为命令执行结果的一部分返回。然而,这里有一个微妙的区别:确保稍后可以从其中一个读取模型查询返回的数据。否则,如果响应未到达客户端,则存在数据丢失的潜在风险。您可以在DanielWhittaker的博客(4)中看到这方面的示例,他在其中讨论了使用命令执行对象来验证命令。此外,在这个要点(5)中,您可以看到我在C#中使用的命令执行结果对象。复杂性陷阱二:事件溯源(EventSourcing)由于历史原因,CQRS与EventSourcing模式密切相关。毕竟,发明CQRS是为了使事件溯源模式成为可能。但是,让我们重新评估这两种模式之间的耦合。正如我之前所说,CQRS的目标是让相同的数据可以用不同的模型表示。如果您使用事件源域模型,则绝对需要CQRS来执行查询。但是,还有许多其他实施CQRS的合法理由与事件源无关。您的系统以不同的表示模型显示其实体。您必须支持不同的查询模型(搜索、图形、文档等)。写作和阅读之间的差异很大,您想独立衡量它们。你不喜欢ORM。这是否意味着在所有这些情况下,您都必须走事件溯源路线?如果这样做,您将陷入复杂性陷阱。事件源是一种对业务领域进行建模的方式,它可能是最复杂的方式。因此,只有在您的业务领域适合您的业务领域时,您才应该采用事件溯源。让我们看看如何在其他情况下实现CQRS。解决方案:CQRS!=EventSourcing我们已经学会了通过编写事件处理程序来生成投影。如何在没有事件的情况下实现投影?还有另一种实现投影的方法,我称之为“基于状态的投影”。这个主题值得单独发表,但我将简要介绍三种实现“基于状态的投影”的方法。1.“脏”标志您可以通过设置IsDirty标志来标记正在更新的实体,并实现一个投影引擎来查询脏实例并将更新的数据投影到不同的模型中。要重建投影,您只需为所有记录设置脏标志。2.追加在关系数据库中,您可以在表层跟踪提交。例如,在SQLServer中,您有一个内置机制,即“rowversion”列。这些功能也可以在其他关系数据库中实现。投影引擎将查询更新的行并以类似于填充的方式投影更新的数据。要从头开始重建投影,最后已知的提交ID必须“回滚”到0。3.数据库视图如果您使用的是关系数据库,并且您所需要的只是一个不同的模型来表示其数据,那么数据库视图可以工作美好的。没错,一个完全有效的CQRS系统可以在数据库中实现。这可能是最不吸引人的解决方案——但它不仅有效,而且自然地遵循CQRS模式。这些投影模型的方法可能不酷或性感,但它们有效。我已经看到很多项目采用这些方法取得了巨大的效果,而没有被不必要地淹没在与事件源相关的复杂性中。等等,我只是建议不惜一切代价忽略事件溯源,因为它很复杂吗?当然不是!事件溯源是您工具箱中最重要的工具之一。但是,与任何工具一样,请在其上下文中使用它——它提供业务价值的业务领域。核心子领域。另一方面,通用子域和支持子域,它们足够简单,可以使用事务脚本或ActiveRecord模式实现,但仍然受益于CQRS。在这种情况下,使用最简单的工具来完成工作并使用基于状态的预测来获得CQRS的好处。复杂性陷阱#3:好东西太多微服务炒作引起了很多人对CQRS的关注:如果您有一组独立的服务需要查询彼此的数据,那么CQRS是通用解决方案(6)。然而,我已经看到这种方法产生了巨大的数据流图,在服务之间投射了大量数据。这不一定是坏事,但在许多情况下,它可能是退后一步并重新考虑分解策略的信号。您的服务可能过于细化,无法反映业务领域的边界。如果是这种情况,您可以通过将服务边界与相应的业务领域重新对齐来大大降低架构的复杂性。CQRS:Deconstruction我想用CQRS的图来概括一下。此图与您在网上找到的其他图不同。这就是我所看到的并实现了CQRS模式。命令有响应。定义的投影机制是抽象的,独立于实现细节。它可能是基于事件的,或者是基于状态的,甚至是数据库视图。最后,没有事件溯源。根据业务领域的要求对系统的业务逻辑进行建模:ActiveRecord、领域模型或事件源领域模型。与每个正确应用的工具一样,CQRS应该降低复杂性,而不是诱导复杂性。如果您的体系结构的复杂性增加,那么您可能做错了。文中相关链接:http://codebetter.com/gregyoung/2010/02/20/cqrs-and-cap-theorem/https://martinfowler.com/bliki/CQRS.htmlhttps://youtu.be/LDW0QWie21s?t=448http://danielwhittaker.me/2016/04/20/how-to-validate-commands-in-a-cqrs-application/https://gist.github.com/vladikk/86da55d0eb09d7a291b9f9a5b406f2c9https://www.ibm.com/developerworks/cloud/library/cl-build-app-using-microservices-and-cqrs-trs/英文原文:https://vladikk.com/2017/03/20/tackling-complexity-in-cqrs/本文转载自微信公众号《高可用架构》,可通过以下二维码关注。转载本文请联系高可用架构公众号。