65.9K
CodeProject 正在变化。 阅读更多。
Home

ESB - 路由

starIconstarIconstarIconstarIconstarIcon

5.00/5 (4投票s)

2018年8月2日

CPOL

9分钟阅读

viewsIcon

10703

downloadIcon

123

关于 ESB 中路由原则的讨论

引言

一篇早期的文章中,我描述了如何为您的 ESB 解决方案添加日志框架,并创建了一个微型应用程序来说明 ESB 的组件。但我并没有深入探讨基本原理、最佳实践、命名约定,或者为了实现一个可行的 ESB 您可能只需要一点点东西。

在本文中,我将通过一个真正微小但高效的 ESB 的最小可行产品(MVP),来展示实现 ESB 基本原理所需的最小投入,并讨论路由部分,提出一些可行的指导方针。我还会触及订阅和转换,但更深入的讨论将留待以后。

要求

ESB 的主要功能是使系统能够发布消息,让其他系统订阅这些消息,并以合适的格式进行传递。功能需求或原则可以被形式化为:

  • ESB 是实时集成
  • ESB 实现发布-订阅模式
  • ESB 流程是异步的
  • ESB 流程是单向的

我认为,让源系统对已发布消息的去向发表意见,是对 ESB 概念的普遍误解。ESB 不是邮局。已发布的消息没有地址。ESB 的核心理念是,源系统在发布数据时,不知道哪些系统会订阅它——这是一种抽象机制,确保发布者和订阅者都可以进行更改,而不会相互影响。

这如何实现?主要技巧是按照预定义的一致格式发布数据。这可以通过源系统直接按照规范发布数据来实现,或者像某些实现(例如 BizzTalk)那样,将接收到的消息转换为内部的规范格式,然后由各个目标系统消费并转换为其所需格式。

无论如何:格式是契约——而不是来源或去向。

架构

从更架构的层面来看,ESB 必须提供:

  • 接收位置
  • 路由机制
  • 转换机制
  • 传递机制

ESB 组件

上图说明了微型演示应用程序中的组件:接收端是 Go 实现的 Web 服务,路由是消息队列——我将使用 RabbitMQ,转换和传递是一个 Go 应用程序。

我将假设传输协议是 HTTP,但这当然是一个简单的实现问题。

对于发布-订阅模式,关键要求是能够订阅特定消息,并限定您想接收哪些消息——这就是路由。

对于 ESB 实现,关键要求是能够以合适的格式传递消息——这就是转换。

路由

为了便于订阅,消息应附带描述性元数据,订阅过滤可以基于这些元数据构建。根据我的经验,最重要的元数据是源系统和消息类型,但消息的任何其他属性都可能对订阅者有吸引力,例如消息年龄、子系统信息或更具体的指标。

源系统是一个有趣的属性,听起来可能很奇怪,但虽然源系统不应该关心或知道其消息的去向,但对于消费系统来说,它们通常关心消息的来源:集成通常是从目标端驱动的,源于它们了解另一个系统中发生情况的需求。因此,自然语言的消息过滤器通常类似于“这是什么,它来自哪里?”

尽管听起来如此,但这并不与抽象冲突。抽象是通过消息类型始终保持一致来实现的——因此来自两个不同商店的订单应该具有相同的格式。(如前所述,ESB 可以预处理消息以获得这种一致性,但这超出了本文的范围)。从运营的角度来看,源系统的事件通常定义了目标系统的兴趣——因此是路由的关键。稍后将详细讨论这一点。

示例:同一消息有多个订阅者

根据我的经验,ESB 通常用于在业务系统(如 CRM 和 ERP 系统、电子商务系统、制造执行系统 (MES) 等)以及数据仓库之间分发数据。消息类型通常反映重要的业务流程事件,例如订单创建、付款、客户、产品 (SKU) 等。

此处描述的事件可以理解为是主数据事件或事务事件。通常,后者(订单、付款)比前者(客户、产品)更常见,这可能表明应首先创建哪些流程。手动对齐主数据,然后实现事务性流程,可能是一种务实的做法。之后,可以评估实现主数据流程是否值得。

您可能会争辩说,消息类型是一种过于粗略的质量属性,您需要更具区分性的东西来确定是否需要该消息。也许您想知道,例如,订单消息是新订单、订单更改还是订单取消。这很可能是事实,当然需要在每种情况下进行考虑。我的经验是,通常您会收到消息中的整个订单,或者您最终会收到整个订单,并且订单会告诉您它是否是您已知的(通过检查唯一的 order_ID),是否已被取消(通常通过某个 order_status),如果都不是,那就是更改——通常最好通过删除现有订单(如果可能)并创建一个新订单来处理。

一个有趣的模式是让消息仅仅是一个事件通知,包含事件类型和涉及的实体 ID。然后,粒度是事件类型。然而,这种模式会导致所有消费者从源系统获取相关数据,这可能会导致过多的流量(至少如果源系统是具有有限流量配额的 SaaS 应用程序,则应考虑这一点)。

根据我的经验,仅基于消息类型进行路由,并在消费者处进行进一步过滤,可以实现更简单的管理(尤其是在大型安装中)。

保持订阅简单也有助于使实现易于理解:例如,在我们的情况下,路由由 RabbitMQ 处理,可以为每个源系统设置一个交换器,路由键等于消息类型,队列标识源、目标和消息类型。例如,如果您在网络商店中发出订单,并且您的 SAP ERP 系统正在消费这些订单,那么您将有一个名为“webstore”的交换器,路由键为“order”,队列名为 webstore-sap-orders。

查看 RabbitMQ 中的队列列表,可以很好地了解谁在使用来自哪里 的数据。

转换

消息可以采取各种形式和形状,但最常见的格式是 JSON 和 XML。同样,传输方法有很多,但如今绝大多数的传输方式是 HTTP——或 HTTPS。

对于生产级系统,您可能会想为 API 添加版本号。您可能希望允许不同级别的有效负载验证,甚至引入根本性的架构更改。通过版本号,您可以做到所有这一切。

示例

我将使用上述关于元数据或路由键的考虑因素,在一个激进的设计中实现 Web 服务方法。我将定义一个通过 URL 调用该方法的方法,其模式如下:

HTTP://<domain>/<sourcesystem>/<routingkey>

基本上,它允许进行以下类型的 POST 调用(本地运行的服务):

URL 本身应该易于理解。消息体将包含实际消息,例如报价、发票、付款详情等。该服务会将消息发送到 RabbitMQ 中以源系统命名的交换器(例如,示例中的 salesforce、zuora、netsuite、sap),并将 URL 中指定的路由键(即示例中的 quote、invoice、payment 和 order)添加进去。

这样做的主要原因是,许多 SaaS 系统都实现了某种事件系统,但并非所有系统都能添加标头字段——这本来是路由键的自然位置。大多数系统都能够定义 Webhook 地址,并可能设置身份验证,通过此模式,路由就成为 Webhook URL 的一部分。

您可能会争辩说,抽象可以再进一步,让 sourcesystem 在方案中由其类型表示,例如 ERP、CRM 等,这样 URL 就可以是:https:///erp/paymenthttps:///store/order。这将允许更多类似的系统使用同一个交换器,例如,如果您有多个网络商店,它们可以在同一个交换器上发送订单。这样,您就可以添加新的源系统而无需更改订阅过滤器。我的经验是,按系统轻松监控消息活动非常重要,我不推荐这种抽象。

这还将允许许多源系统/发布者将消息发送到同一个端点。

为了允许源系统添加更多路由键,可以实现一个响应此模式的 API 调用服务:

HTTP://<domain>/<sourcesystem> + 路由键标头:“routingkeys”:”routingkeys string”。为简单起见,标头中的路由键字符串可能采用与 RabbitMQ 相同的格式:由点分隔的关键字,最大长度为 255 个字符。

Using the Code

在附件的应用程序文件中,您将找到 Web 服务和一个简单的消费者实现,该消费者不执行任何转换。我将在以后的文章中讨论消息格式和转换。在您的计算机上使用标准的 RabbitMQ 安装,应该可以轻松运行这两个 Go 应用程序,并看到交换器和队列随着传入消息被创建,以及消费者实际从队列中检索消息。

我相信这两个 Go 脚本都可以进行优化,所以还有很多工作要做!

© . All rights reserved.