目录

第二章 事件驱动架构 (Event-Driven Architecture)

事件驱动架构模式是一种流行的分布式异步架构模式,用于构建高度可扩展的应用程序。它具有很强的适应性,既适用于小型应用程序,也适用于大型复杂应用程序。事件驱动架构由高度解耦的、单一用途的事件处理组件构成,这些组件异步接收和处理事件。

事件驱动架构模式包含两种主要拓扑结构:中介器拓扑和代理拓扑。中介器拓扑通常用于需要通过中央中介器协调事件中的多个步骤的情况,而代理拓扑则用于无需中央中介器即可将事件链接在一起的情况。由于这两种拓扑结构的架构特性和实现策略有所不同,因此了解每种拓扑结构对于确定哪种最适合您的特定情况至关重要。

中介器拓扑 (Mediator Topology)

中介器拓扑结构适用于包含多个步骤且需要一定程度编排才能处理的事件。例如,执行股票交易可能需要您先验证交易,然后检查该股票交易是否符合各种合规规则,将交易分配给经纪商,计算佣金,最后通过该经纪商执行交易。所有这些步骤都需要一定程度的编排来确定步骤顺序以及哪些步骤可以串行执行,哪些步骤可以并行执行。

中介器拓扑结构中包含四种主要的架构组件:事件队列、事件中介器、事件通道和事件处理器。事件流始于客户端向事件队列发送事件,事件队列用于将事件传输到事件中介器。事件中介器接收初始事件,并通过向事件通道发送额外的异步事件来编排该事件,从而执行流程的每个步骤。监听事件通道的事件处理器从事件中介器接收事件,并执行特定的业务逻辑来处理该事件。图 2-1 展示了事件驱动架构模式的一般中介器拓扑结构。

图 2-1. 事件驱动架构中介器拓扑
图 2-1. 事件驱动架构中介器拓扑

在事件驱动架构中,通常会有十几个到几百个事件队列。该模式并未指定事件队列组件的具体实现方式;它可以是消息队列、Web 服务端点,或二者的任意组合。

此模式包含两种类型的事件:初始事件和处理事件。初始事件是中介器接收到的原始事件,而处理事件是由中介器生成并由事件处理组件接收的事件。

事件中介器组件负责协调初始事件中包含的各个步骤。对于初始事件中的每个步骤,事件中介器都会向事件通道发送一个特定的处理事件,该事件随后会被事件处理器接收和处理。需要注意的是,事件中介器实际上并不执行处理初始事件所需的业务逻辑;它只是知道处理初始事件所需的步骤。

事件通道由事件中介器使用,用于异步地将与初始事件中每个步骤相关的特定处理事件传递给事件处理器。事件通道可以是消息队列或消息主题,但消息主题在中介器拓扑结构中最常用,这样处理事件就可以由多个事件处理器处理(每个事件处理器根据接收到的处理事件执行不同的任务)。

事件处理器组件包含处理处理事件所需的应用程序业务逻辑。事件处理器是自包含、独立且高度解耦的架构组件,它们在应用程序或系统中执行特定任务。虽然事件处理器组件的粒度可以从细粒度(例如,计算订单的销售税)到粗粒度(例如,处理保险索赔)不等,但重要的是要记住,通常情况下,每个事件处理器组件都应该执行单个业务任务,而不应该依赖其他事件处理器来完成其特定任务。

事件中介器可以通过多种方式实现。作为架构师,您应该了解每种实现选项,以确保您选择的事件中介器解决方案符合您的需求和要求。

事件中介器最简单、最常见的实现方式是通过开源集成中心,例如 Spring Integration、Apache Camel 或 Mule ESB。这些开源集成中心中的事件流通常使用 Java 代码或领域特定语言 (DSL) 来实现。对于更复杂的中介和编排,可以使用业务流程执行语言 (BPEL) 以及 BPEL 引擎,例如开源的 Apache ODE。BPEL 是一种标准的类 XML 语言,用于描述处理初始事件所需的数据和步骤。对于需要更复杂编排(包括涉及人机交互的步骤)的大型应用程序,可以使用业务流程管理器 (BPM)(例如 jBPM)来实现事件中介器。

了解自身需求并将其与正确的事件中介器实现相匹配,对于任何使用此拓扑结构的事件驱动架构的成功都至关重要。使用开源集成中心进行非常复杂的业务流程管理编排注定会失败,就像使用 BPM 解决方案来执行简单的路由逻辑一样。

为了说明中介器拓扑结构的工作原理,假设您通过一家保险公司投保,并且决定搬家。在这种情况下,初始事件可以称为搬迁事件。处理搬迁事件的步骤包含在事件中介器中,如图 2-2 所示。对于每个初始事件步骤,事件中介器会创建一个处理事件(例如,更改地址、重新计算报价等),将该处理事件发送到事件通道,并等待相应的事件处理器(例如,客户处理、报价处理等)处理该处理事件。此过程持续进行,直到初始事件中的所有步骤都已处理完毕。事件中介器中“重新计算报价”和“更新理赔”步骤上方的单横线表示这些步骤可以同时运行。

图 2-2. 中介器拓扑示例
图 2-2. 中介器拓扑示例

代理拓扑 (Broker Topology)

代理拓扑结构与中介器拓扑结构的区别在于,代理拓扑结构没有中央事件中介器;消息流通过轻量级消息代理(例如 ActiveMQ、HornetQ 等)以链式方式分布在各个事件处理器组件上。这种拓扑结构适用于事件处理流程相对简单,且不需要(或不希望)集中式事件编排的情况。

代理拓扑结构中包含两种主要的架构组件:代理组件和事件处理器组件。代理组件可以是集中式的,也可以是联合式的,它包含事件流中使用的所有事件通道。

代理组件中包含的事件通道可以是消息队列、消息主题,或二者的组合。

图 2-3 展示了这种拓扑结构。如图所示,没有中央事件中介器组件来控制和协调初始事件;相反,每个事件处理器组件负责处理事件并发布一个新事件来指示其刚刚执行的操作。例如,一个平衡股票投资组合的事件处理器可能会收到一个名为股票拆分的初始事件。基于该初始事件,事件处理器可能会进行一些投资组合再平衡操作,然后向经纪商发布一个名为重新平衡投资组合的新事件,该事件随后会被另一个事件处理器接收。需要注意的是,有时某个事件处理器会发布一个事件,但该事件不会被任何其他事件处理器接收。这种情况在应用程序演进或为未来的功能和扩展做准备时很常见。

图 2-3. 事件驱动架构代理拓扑
图 2-3. 事件驱动架构代理拓扑

为了说明代理拓扑的工作原理,我们将使用与中介器拓扑相同的示例(被保险人搬家)。由于代理拓扑中没有中央事件中介器来接收初始事件,客户处理组件直接接收该事件,更改客户地址,并发出一个事件表明其已更改客户地址(例如,地址变更事件)。在本例中,有两个事件处理器对地址变更事件感兴趣:报价处理组件和理赔处理组件。报价处理组件根据地址变更重新计算新的汽车保险费率,并向系统的其他部分发布一个事件,表明其执行的操作(例如,重新计算报价事件)。另一方面,理赔处理组件接收到相同的地址变更事件,但在此情况下,它会更新未决的保险索赔,并向系统发布一个更新索赔事件。这些新事件随后会被其他事件处理器组件接收,事件链在系统中持续传递,直到没有针对该特定初始事件发布新的事件为止。

图 2-4. 代理拓扑示例
图 2-4. 代理拓扑示例

如图 2-4 所示,代理拓扑的核心在于将事件串联起来以执行业务功能。理解代理拓扑的最佳方式是将其想象成一场接力赛。在接力赛中,运动员手持接力棒跑一段距离,然后将接力棒交给下一位运动员,以此类推,直到最后一名运动员冲过终点线。在接力赛中,一旦运动员交出接力棒,比赛就结束了。代理拓扑也是如此:一旦事件处理器交出事件,它就不再参与该特定事件的处理。

注意事项

事件驱动架构模式的实现相对复杂,这主要是由于其异步分布式特性。在实现此模式时,必须解决各种分布式架构问题,例如远程进程的可用性、响应速度以及代理或中介器故障时的代理重连逻辑。

选择此架构模式时需要考虑的一个因素是单个业务流程缺乏原子事务。由于事件处理器组件高度解耦且分布式,因此很难跨组件维护事务性工作单元。因此,在使用此模式设计应用程序时,必须不断思考哪些事件可以独立运行,哪些事件不能独立运行,并据此规划事件处理器的粒度。如果您发现需要将单个工作单元拆分到多个事件处理器中——也就是说,如果您为本应是一个不可分割的事务使用了不同的处理器——那么这可能不是适合您应用程序的模式。

事件驱动架构模式最困难的方面之一或许是事件处理器组件契约的创建、维护和管理。每个事件通常都有一个与之关联的特定契约(例如,传递给事件处理器的数据值和数据格式)。使用此模式时,从一开始就确定标准数据格式(例如,XML、JSON、Java 对象等)并建立契约版本控制策略至关重要。

模式分析

下表对事件驱动架构模式的常见架构特征进行了评级和分析。每个特征的评级基于该特征作为典型模式实现能力的自然倾向,以及该模式的普遍认知。如需了解此模式与本报告中其他模式的并排比较,请参阅本报告末尾的附录 A。

整体敏捷性

等级: 高
分析: 整体敏捷性是指快速响应不断变化的环境的能力。由于事件处理器组件功能单一且与其他事件处理器组件完全解耦,因此变更通常仅限于一个或几个事件处理器,并且可以快速完成,而不会影响其他组件。

易于部署

等级: 高
分析: 总体而言,由于事件处理器组件的解耦特性,这种模式部署起来相对容易。代理拓扑结构比中介器拓扑结构更容易部署,这主要是因为事件中介器组件与事件处理器组件的耦合度较高:事件处理器组件的更改可能也需要事件中介器的更改,因此任何更改都需要同时部署两者。

可测试性

等级: 低
分析: 虽然单个单元测试本身并不难,但确实需要某种专门的测试客户端或测试工具来生成事件。此外,这种模式的异步特性也增加了测试的复杂性。

性能

等级: 高
分析: 虽然由于涉及大量消息传递基础设施,事件驱动架构的实现可能会出现性能不佳的情况,但总的来说,该模式通过其异步功能实现了高性能;换句话说,执行解耦的并行异步操作的能力超过了消息排队和出队的成本。

可扩展性

等级: 高
分析: 这种模式下,可扩展性自然而然地通过高度独立和解耦的事件处理器来实现。每个事件处理器都可以单独扩展,从而实现细粒度的扩展。

易于开发

等级: 低
分析: 由于该模式的异步特性,以及契约创建和代码中需要更高级的错误处理条件来处理无响应的事件处理器和故障的代理,开发可能会变得有些复杂。

在 GitHub 上查看本页或提交勘误 ↗