cqrs axon_在Axon框架下与Allard Buijze讨论CQRS

cqrs axon

Axon框架是命令和查询责任隔离(CQRS)模式的Java实现, 正如Axon Framework网站非常描述的那样,Axon框架是“一种明确区分命令处理的体系结构模式-告诉应用程序要做什么。执行-和查询执行-可以洞察应用程序的状态。” 在发布Axon框架0.4迭代之前不久,InfoQ与它的创建者Allard Buijze进行了交谈,以了解更多信息。

InfoQ:您能否大致描述Axon是什么以及其构想的原因?

Allard Buijze(AB): CQRS是一种体系结构模式,可以在应用程序的两个部分之间进行明显区分:一个部分处理命令,另一个部分执行对数据的查询。 我们之所以会如此区分,是因为两者的(非功能性)需求之间存在重大差异。 应用程序的命令处理部分负责命令的验证和执行。 命令区域的确保留有关应用程序状态的信息,但不公开该状态。 相反,它公开了反映发生的任何更改的事件。 这些事件由事件侦听器接收,这些事件侦听器更新查询数据源或基于这些事件对数据进行进一步处理。 实现CQRS体系结构需要大量的“管道”和样板代码。 大部分代码与项目无关,并且很容易出错。 Axon旨在提供大多数CQRS风格的应用程序所需的基本构建块。 例如,事件分派通常是一个异步过程,但是您确实希望保证交付,在某些情况下还希望有顺序交付。 Axon提供了实现这些功能的构件和指南。 除了这些核心组件之外,Axon还希望与第三方库集成。 考虑一下框架和库,例如JMS,MQ和Spring Integration。

InfoQ:您能描述一个简单的解决方案以及所涉及的参与者吗? 即,我将使用哪些类(或概念)来构建将地址添加到地址簿的解决方案?

AB:假设我们有一个通讯录,可以用来存储朋友和亲戚的地址。 但是为了使其更具功能性,我们将实现一项功能,使您可以更改每个条目的地址。

首先,我们需要一个组件,该组件将接收命令以插入条目,对其进行编辑,然后(发生这些情况)再次将其删除。 一个简单(但有效)的解决方案是精简服务层。 另一个选择可以是命令模式的实现。 现在选择服务并实现“添加条目”功能。 然后,服务实现将创建一个地址簿条目对象(“ AddressEntry”)并将其发送到存储库。 该存储库将保留该条目,并告诉事件总线将“ AddressEntryAddedEvent”异步调度到应用程序的其他部分。 客户将收到地址已更改的确认。 保留条目的存储库除了提供标识符外,不提供任何查询存储信息的方法。 这使我们可以选择非常专注于存储对象树并通过ID检索它们的存储。 有许多解决方案可以很好地做到这一点,它们的扩展性比传统的DBMS更好:NoSQL数据库,分布式缓存等。即使是普通的旧文件系统,有时也会提供比DBMS更好的性能。 如果您只能按ID进行搜索,则通讯簿将毫无用处。 因此,我们需要一种查询此信息的方法。 在CQRS中,这是与处理命令的组件完全不同的组件。 这是事件重新回到故事中的地方。 事件侦听器将拾取与"AddressEntry"相关事件有关的事件,并将它们表示的更改存储到DBMS中。 这次,我们可能会选择MySQLPostgresSQL之类的解决方案因为我们想提供不同的搜索和查询可能性。 现在,我们有了一个处理命令的组件:我们的服务,一个跟踪条目实际状态的组件:存储库,一个更新查询数据库的组件:事件侦听器。 但是仍然没有办法将这些信息呈现给我们选择的任何前端。 我们需要的最后一个组件是数据访问组件。 一个非常薄的组件仅查询数据。 该数据层会将DTO (通常是只读)返回到包含查询结果的前端。

由于一张图片说了1000多个字,因此以下是其中涉及的组件的概述:

InfoQ:为什么有人会选择这项技术,而不是与服务结合使用的更传统的贫血领域模型?

AB: CQRS并不排除使用贫血模型本身。 如果命令处理组件中的逻辑非常简单,则可以使用“事务脚本模式”来实现该逻辑,其中业务逻辑包含在服务中。 与一般的Rich和贫血领域模型讨论中的优点和缺点相同。 该技术与传统实现真正不同之处在于查询逻辑和命令逻辑的分离。 应用程序的命令区域负责维护应用程序当前状态的视图,以便可以对传入的命令进行验证。 对该模型的任何更改仅通过在此信息更改时发布的事件来公开。 该体系结构的最大优点是,它允许您设计模型以满足特定要求。 通常,更改数据的要求与查询数据的要求明显不同。 这些要求之间冲突的一个典型示例通常可以从许多SQL语句的大小中看出。 SQL优化通常用于从并非真正旨在执行该查询的数据源中挤出最佳性能。

另一个优点是应用程序可扩展性。 在更传统的分层体系结构中,服务经常需要彼此来完成其工作。 尤其是在长期运行的项目中,结果是一团糟,只有开发人员才能维护。 CQRS通过事件强制组件之间的松散耦合。 您如何控制通过对存储库进行更新而发布的事件的模型/粒度? 如果我发送了一个更新客户信用卡号的命令,而没有发送其他任何消息,那么是否发布了客户更新事件? 事件是否包括更新的信用卡数据? 或者,仅发布一个客户更新的事件并期望客户知道老客户和新客户之间的唯一差异就是更​​新的信用卡号,是否合理?

在良好的CQRS实施中,所有更改都会导致事件。 通常,命令和事件成对存在。 因此,对于UpdateCreditCardNumberCommand ,您将具有CreditCardNumberUpdatedEvent 。 此事件必须包含已更改的数据,因为没有其他信息来源。 这意味着该事件将包含其号码已更改的客户的标识符以及新的(可能是旧的)信用卡号。

其中一个组件可能只是将此新信息存储在数据库中,而另一个组件可能开始进行交易以验证信用卡。

在某些情况下,仅通知更改是不够的。 您想知道“为什么”信息已更改。 例如,地址更改可能是拼写错误或实际上已转移到另一个地址的某人的记录。 在后一种情况下,您可能希望触发自动发送明信片的过程,以祝贺他们的新家。 在第一种情况下,情况并非如此。

这意味着命令和事件都应指示更改的意图。 然后,您将对事件进行不同的建模。 您将拥有一个抽象类,该抽象类定义了地址已更改的事实,以及该类的两个具体子类指示了此更改的不同意图。 不关心意图的事件处理程序(例如更新数据库表的事件处理程序)可以关注抽象事件类型的发生。

InfoQ:我们都听说过分段事件驱动架构(SEDA),并且了解缓冲处理的价值,直到下游组件可以赶上时为止,但是看起来我们在这里缓冲着对记录存储本身的更新。 。 因此,数据库永远不会过载,这是合乎需要的,但是当您需要同步结果时如何处理情况呢? 例如,假设有一个系统,其中一个客户在下午2:05更新客户的银行帐户余额,而另一个客户在下午2:07加载查询。 让我们进一步假设,第一次更新-下午2:05-尚未传播到记录存储(因为它仍在排队)。 因此,尚未发出通知客户新状态的事件。 这是否表示读取不一致? 还是CQRS暗示查询是针对事件创建的审核线索,而不是记录存储?

AB:当您想到它时,我们已经愚弄了很长时间。 我们让自己和我们的应用程序用户相信他们是唯一使用应用程序的人。 但是,现实世界是不同的。 有多个用户(无论是否愿意)都对同一数据进行操作,而这些参与者中每个参与者所依据的数据都是陈旧的。 协作和陈旧这两个事实是CQRS背后的驱动力。 CQRS承认过时的事实,并利用过时的优势。 副作用可能是陈旧性窗口有所增加。 但是,这仅适用于应用程序的查询部分。 命令部分始终是最新的。 如果多个命令要作用于同一聚合,则它们将不得不彼此等待。 在这种情况下,由前端决定是否要等待命令执行结果。

大多数命令记录的是现实世界中发生的事实,需要在应用程序中反映出来,例如地址更改。 用户真正感兴趣的是系统是否已接收到地址更改命令并将对其进行处理。 没问题,此过程需要24小时。 许多公司将感谢用户提供的信息,并在处理命令后发送电子邮件确认。

但是,命令的验证是另外一回事了。 例如,如果门牌号字段丢失,则希望能够向用户提供快速反馈。 在该示例中,查询用户将只读取陈旧数据。 在这种情况下,它已经过了2分钟,因此非常陈旧。 但是,那真的是一件坏事吗? 如果第一个用户两分钟后输入了他的数据,那么我们根本就不会认为这是一个问题。 实际上,您可能会说根本无法获得一致的阅读。 当数据显示在屏幕上时,其他用户可能同时已对该数据进行了操作。

例如,某些富客户端的开发方式可以使它们直接在屏幕上处理传入事件。 这将在用户屏幕上相当直接地反映出对模型的所有更改(尽管仍会稍有延迟)。 股票经纪申请也做类似的事情。 即使到那里,实际的股票价格也永远不会显示在屏幕上。 它总是过时的。

InfoQ:为什么使用Spring Integration作为消息传递总线? 例如,为什么不嵌入Mule,也可以使用Spring进行管理?

AB:目前,该框架专注于为单个JVM提供构建模块。 但是,下一步是提供简单的配置选项和构建块,以允许不同JVM中的实现相互保持更新。 既然我们已经意识到数据是过时的事实,那么我们可以使用现有的事件和命令,然后使用现有技术将它们发布到其他JVM。 在早期提供Spring Integration支持的选择主要是受时间限制的推动。 Spring Integration已经具有许多消息传递实现的连接器,并且连接到Spring Integration非常简单。 将来,Axon可能会包含用于其他事件和消息相关技术的连接器,例如Mule,Apache Camel,MQ和JMS。

InfoQ:您将轻松集成描述为主要优势。 该描述暗示了中心辐射型架构使合作伙伴应用程序更容易拥有数据的“视图”,它们可以针对用例进行构建和优化。 您是否认为复杂事件处理对于这种事情很有用,可以将消息传递中的模式显示为聚合事件?

AB:我想你在那头上打了钉子。 由于应用程序中的解耦,集成变得更加容易。 如果您需要用于集成的数据,则可以维护一个单独的数据源,其中完全按照需要的方式包含数据。 复杂事件处理是我实际上经常使用的一个集成示例。 它可以以完全非侵入性的方式实现。 以欺诈检测为例。 您可能有一个只监听应用程序中所有事件的组件。 如果发生某种事件模式,则可以向应用程序发送命令以阻止某个用户帐户。

InfoQ:您能想到哪些真实的用例,说明这种架构的价值? 例如,是否可以将其应用到像e-Bay这样的最终一致的架构中,该架构可以处理分片数据库中的许多非XA事务?

AB:有几种类型的应用程序可以从该体系结构中受益。 但是,CQRS的某些原理和思想最终将使任何大中型应用受益。

提供有关相同数据的视图的应用程序就是这样的一组。 CQRS使用事件来更新查询数据。 不管有一个表显示此数据,还是有数百个表,每个事件侦听器组件都将保持其自己的数据源为最新状态。 信息视图的示例包括视图页面,搜索引擎索引,报告信息,“上个月发生了什么”电子邮件,临时电子邮件通知等,等等。LinkedIn是此类应用程序的示例。

另一个例子是将来可能会扩展的应用程序。 由于事件在任何CQRS应用程序中无处不在,因此创建可对这些事件起作用的附件非常容易。 与采用传统方法相比,变更通常不会带来太大的干扰。 例如,由于预算限制,某些网上商店的实施需要从小做起。 但是,一段时间后,他们可能需要一个库存功能,该功能在下订单时会自动更新。 也许过了一段时间,运输部门需要随时了解有关准备运输的订单的最新信息。

最后一种应用程序是需要扩展的应用程序。 传统的分层体系结构不能很好地支持可伸缩性。 在扩展环境中,事务管理是一个非常繁重的过程。 XA交易要求为每笔交易付出高昂的代价,而实际上只有一对多的交易会出错。 CQRS通过事件使用异步更新。 如果发现任何冲突事件(例如,购买了某商品,但该商品没有库存),我们需要通过补偿性交易来解决。 这实际上是ACID与BASE的讨论。 这确实是我们(作为开发人员)必须习惯的东西。 我们必须教育自己和我们的客户,时常会出错。 而不是向用户显示错误(默认为“请稍后重试”屏幕),我们尝试在后台解决该错误。

如何查询信息(例如在您描述的分片数据库中)实际上取决于开发人员。 使用CQRS,您可以为应用程序的不同部分做出不同的选择。 所有这些不同部分都由相同的来源更新:事件。

Axon的主要目标是使CQRS实现的设置更加容易。 为此,我们提供了负责事件调度的基本实现以及此类体系结构中一些常用组件的基本抽象实现。 后者的示例是使用事件源或更新关系数据库中信息的事件处理程序的存储库的基本实现。

InfoQ:您还要添加什么吗? 我是否错过了您认为应该添加的重要内容?

我个人非常喜欢的CQRS的另一个功能是“事件源”。 在更传统的分层体系结构中,审计跟踪通常作为它们描述的实际更改的副作用而实现。 这使得审核跟踪非常容易出错,因此不可靠。 事件源使用不同的方法:让您的事件成为所有更改的源头。 换句话说,事件将导致更改。

实际上,这意味着聚合上的方法(该术语由Domain Driven Design定义 Eric Evans定义)不会直接更改其状态。 相反,它们将生成en事件,然后将其应用于聚合。 当聚合存储在存储库中时,生成的事件将分派到应用程序的其余部分。 在这种情况下,存储库将不会存储聚合本身,而只会存储最新(未提交)的事件。 产生的事件流是聚合上曾经发生的所有更改的概述。 如果存储库需要从存储中重建聚合,它将重播所有过去的事件。 事件源提供的机会不仅限于审计跟踪。 您还可以使用这些事件将应用程序及时设置为特定状态,并在组件上重播事件以进行调试。 Axon包含一些构建基块,使使用事件源变得更加容易。 有一个聚合的抽象实现,您可以使用特定于案例的业务逻辑来扩展该聚合,而可以将存储事件存储在文件系统中的存储库是一个基本实现。

翻译自: https://www.infoq.com/articles/cqrs_with_axon_framework/?topicPageSponsorship=c1246725-b0a7-43a6-9ef9-68102c8d48e1

cqrs axon

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值