此模式是 "遗留系统置换模式" 的一部分

事件拦截

拦截对系统状态的任何更新,并将其中一些路由到新组件

2024 年 3 月 5 日

Ian CartwrightRob HornJames Lewis

当我们希望一次替换一个遗留系统时,我们会寻找识别、提取和替换应用程序功能的方法。为此,我们将介绍遗留系统及其替换都需要交互的情况 - 无论是处理状态更改、命令处理、查询还是用户交互。通常,遗留系统难以或成本高昂地更改(解决此挑战可能是置换计划的首要原因),因此我们需要一种机制,允许新系统提供的新的功能集成,同时最大程度地减少对遗留系统的影响。

通过事件拦截,我们识别遗留组件之间现有的集成点,如果可能,利用它们作为我们可以用来引入新功能的接缝。

工作原理

legacy systems may have numerous targets for Event Interception

遗留系统可能并且通常确实具有许多事件拦截目标,包括消息消费者、http API、SQL 连接、批处理作业等。

我们利用遗留组件之间现有的集成点(即上面的“技术接缝”),使我们能够分解、提取或与新的应用程序组件集成。事件拦截是一种利用这些技术接缝的技术,它通过允许从一个组件传递到另一个组件的事件被拦截并路由到新的组件/服务来实现。当我们这样做时,我们希望

  • 最大程度地减少对遗留组件的更改 - 无论是创建事件的组件还是使用事件的组件
  • 允许将事件路由到新系统 - 无论是否重复

消息基础设施

如果消息基础设施已经存在,那么您很幸运 - 它的主要优势之一是它将生产者与消费者的消息解耦。

Messaging decouples producer from consumer

线路监听消息路由器基于内容的路由器 等模式可用于拦截和/或创建可以由新系统或现有遗留系统处理的事件。消息基础设施通常允许使用消息头来过滤消息,使某些消息可供消费者使用。例如,JMS 实现使用消息选择器来允许消息提供者过滤消息。通过这种方式,可以实现简单的拦截和路由。

Message Routing with Message Selectors

在上图中,遗留消费者需要将其生产者配置为包含消息选择器(以有效地添加过滤器)

更复杂的路由可能需要检查消息正文 - 在这种情况下(基于内容的路由),消息需要由路由组件使用,检查消息有效负载,并将消息根据消息内容重新排队到新的目标队列中。

Content Based Message Routing

同样,遗留消费者需要重新配置以从新的目标队列中使用消息

反向代理 - 基于 URL、查询参数或表单数据的路由
反向代理可能是创建和/或拦截事件的另一种非常有效的方法 - 允许您将 http(s) 请求重定向到不同的资源。与消息基础设施类似 - 反向代理通常能够在简单地检查主机和端口部分、路径、查询参数和标头后路由请求。

Reverse                                                                         Proxy                                                                         Routing                                                                         Requests

根据表单数据的内容路由请求可能需要自定义实现,但可以实现很大的灵活性。

API 网关

API 网关在服务的发布端点及其实现之间提供了一层间接层。在粗粒度级别,API 网关使我们有机会应用 按抽象分支 模式,允许网关将所有请求路由到我们选择的实现。在更细粒度的级别,它也可能能够根据请求或有效负载的内容路由服务请求。

在 Web 应用程序中 - 渐进增强

这里的想法是对遗留 Web 应用程序进行小的更改,这将使现代化工作的发布周期与对遗留应用程序的进一步更改分离。为此,您可以将脚本元素添加到模板(或范围内的每个页面)。该脚本可以随着时间的推移而开发(并独立发布),并使用渐进增强来拦截用户操作并根据需要更改行为。例如,您可以使用这种方法来附加或覆盖事件处理程序以更改表单提交的 URL。在添加渐进增强之前

Before using Progressive Enhancement to intercept events

以及之后

Using Progressive Enhancement to intercept events

我们看到这种方法成功使用的一个例子是在替换 Web 店面中的遗留产品图片解决方案时。我们构建了一个新系统,使用户能够提交产品的多个图片,并使专业用户能够对它们进行质量保证。我们能够对遗留店面的全局模板进行简单的更改以添加一个脚本标签,该标签的 Javascript 内容可以独立发布。该脚本在店面内使用渐进增强来检查新系统是否具有产品的图片,如果有,则为这些图片创建一个轮播。显示具有多个详细图片的先进轮播使销售价格能够提高。

数据库层 - 触发器

到更改已进入遗留数据库时,您可以争辩说,事件拦截已经太迟了。也就是说,“预提交”触发器可用于拦截数据库写入事件并采取不同的操作。例如,可以将一行插入到单独的事件表中,以便由新组件读取/处理 - 同时像以前一样继续写入(或中止写入)。请注意,如果您更改了现有的写入行为,则应格外小心,因为您可能会破坏重要的隐式契约。

案例研究:增量域提取

我们的一支团队正在为一位客户工作,该客户的遗留系统存在稳定性问题,并且难以维护和更新缓慢。

该组织正在寻求解决这个问题,并且已经决定,他们最合适的解决方法是用基于服务的架构实现的功能来取代遗留系统。

该团队采用的策略是使用 绞杀者模式 并一次提取一个域,直到几乎没有原始应用程序剩余。其他需要考虑的因素包括

  • 需要继续使用遗留系统而不会中断
  • 需要继续允许对遗留系统进行维护和增强(尽管允许最大程度地减少对正在提取的域的更改)
  • 对遗留应用程序的更改应降至最低 - 遗留系统的保留知识非常匮乏

遗留状态

下图显示了遗留架构的架构。单体系统的架构主要是 表示层-域-数据层

阶段 1 - 为单个域暗启动服务

首先,该团队为单个业务域创建了一组服务,以及使这些服务公开的数据与遗留系统保持同步的功能。

这些服务使用 暗启动 - 即没有被任何消费者使用,而是这些服务允许该团队验证数据迁移和同步是否与遗留数据存储实现了 100% 的一致性。如果存在与协调检查相关的问题,该团队可以推理并修复它们,确保在没有业务影响的情况下实现一致性。

历史数据的迁移是通过“一次性”数据迁移过程实现的。虽然不严格地属于 事件拦截,但持续同步是通过变更数据捕获 (CDC) 过程实现的。

阶段 2 - 拦截所有读取并重定向到新服务

在阶段 2,该团队更新了遗留持久层以拦截并重定向所有读取操作(对于此域)以从新的域服务中检索数据。写入操作仍然使用遗留数据存储。这是一个 按抽象分支 的示例 - 持久层的接口保持不变,并实施了新的底层实现。

阶段 3 - 拦截所有写入并重定向到新服务

在阶段 3,发生了一些变化。写入操作(对于该域)被拦截并重定向以在新的域服务中创建/更新/删除数据。

此更改使新的域服务成为此数据的系统记录,因为遗留数据存储不再更新。任何对该数据的下游使用(例如报告)也必须迁移以成为新域服务的一部分或使用新域服务。

阶段 4 - 将域业务规则/逻辑迁移到新服务

在阶段 4,业务逻辑被迁移到新的域服务中(将它们从贫血的“数据服务”转变为真正的业务服务)。前端保持不变,现在使用遗留外观,将实现重定向到新的域服务。

何时使用它

如果您使用绞杀者模式,那么您也会使用某种形式的事件拦截。如果您有创意,您会发现有很多地方可以使用这种模式创建接缝。

一些替代方法/注意事项

人类事件拦截器

假设事件源于用户操作,可能有机会通过让用户执行其他操作来拦截源头的事件!重新设计业务流程以充分利用现代化工作可能意味着遗留事件不再适用。如果它们确实适用,则可能需要权衡较低质量的用户体验和潜在的运营效率降低,以换取根本不需要创建技术接缝。所谓的“旋转椅集成”,要求用户在新系统上执行某些操作,并在旧系统上执行其他操作,可能会提供您需要快速进行遗留部分置换的接缝。

数据库触发器 - 提交后

“提交后”触发器不会拦截写入事件,但也可以用于创建事件以通知其他系统已发生特定状态更改。

变更数据捕获 (CDC)

CDC 包含允许您从附加到数据库事务日志的条目创建事件流的技术。例如,我们的团队在使用 Debezium 创建可以由新应用程序使用的 Kafka 事件流方面取得了良好的经验。同样 - 不严格地属于拦截,事件流可以由并行运行的新系统使用。

在遗留系统中路由事件

事件拦截的目标之一是最小化或避免更新遗留系统以实现与新系统的集成,但一个明显的替代方案是,如果可以简单地修改调用者以直接调用新系统,而不是引入拦截器。一些需要考虑的权衡

  • 关注点分离和所需复杂性。
    调用者将需要承担所有功能和可操作性问题,否则这些问题将由拦截器承担。例如,了解新系统接口的契约、消息结构、错误处理、日志记录、监控和警报等。
  • 避免额外复杂性。
    修改遗留系统将避免需要在混合中添加另一个 过渡架构。这意味着更少的出错可能性,更少的运营负担,以及降低认知负荷。
  • 当一个集成有多个调用者访问同一个接口,并且该接口的提供者正在被替换时,可能值得考虑使用 遗留模拟 模式。新组件将实现遗留接口,并承担事件拦截器的责任,而无需添加额外的 过渡架构

进一步阅读

此模式和 遗留模拟 是实现 Martin 初始文章中更通用的 绞杀者无花果 模式的示例。

此模式最初由 Martin Fowler 于 2004 年在此网站上发布为博客条目,本文字取代了该描述。

重大修订

2024 年 3 月 5 日