第一部分:微服务和传输器简介

2025-06-07

第一部分:微服务和传输器简介

John 是 NestJS 核心团队的成员

介绍

本系列文章涵盖了如何将 NestJS 应用程序与其他应用程序和服务集成。这可以通过几种不同的方式实现;本系列介绍的方法是使用Nest 微服务和消息代理的组合作为粘合剂。换句话说,Nest 应用程序和外部应用程序通过消息代理异步地相互通信。

在这些示例中,我们使用NATS作为消息代理。注意:本文中的一些概念是所有 Nest 微服务传输器所共有的,而其他一些概念则是 NATS 所特有的。我会在文中尽力指出这些差异。

源代码

可在此处获取工作示例和所有源代码。如果您按照代码库的说明操作,文章效果会更好。完整的安装说明以及其他详细信息可在此处获取。

Nest微服务背景

Nest 的微服务包是那些可能有点难以理解的技术之一。这并不是说它过于复杂——相反,它提供了一个非常简单的应用程序编程模型。我认为这种混淆源于“微服务”一词的使用,这个词已经被过度使用,几乎毫无意义,而且 Nest 的微服务包支持几个截然不同的用例:

  • 在内部用作传输层,开发人员几乎无需了解太多细节即可有效使用它。它只是“工作”,并提供了一种机制,用于为您的 Nest 应用程序添加可扩展性和容错性等功能。

  • 作为一种集成技术,我们需要更深入地挖掘。由于微服务包扮演着双重角色,因此在使用它进行集成时,一些重要的实现细节可能会浮现出来,并可能造成阻碍。幸运的是,正如通常的情况一样,Nest 架构的优雅提供了使这种用例成为可能的机制(即使不是立即显而易见的)。这个主题是本系列文章的重点。

文章系列概述

本系列文章涵盖了集成用例。首先,本文将介绍一些 Nest 微服务基础知识。即使您使用的是上述第一个用例(Nest 应用间通信层)的包,您可能也会对其中一些基础知识感兴趣。文章的组织方式经过精心设计,您可以根据需要跳过基础知识部分;或者,如果您想稍后再回来参考,也可以只阅读您需要的部分。

  • 第一部分(本文!)涵盖了入门内容,包括基本的微服务通信模型、代理以及 Nest 如何使用发布/订阅和请求/响应通信模型。它介绍了本系列文章中将要介绍的外部集成用例。此外,它还列出了用于描述这些有时很复杂的交互中所有活动部分的词汇表。
  • 第二部分将更深入地探讨 Nest 与外部系统交互时的操作方式,并介绍两个简单的外部(非 Nest 的、基于 NATS 的)应用程序,我们将在集成案例研究中使用它们。此外,本文还介绍了 Nest 使用专有消息格式可能带来的挑战。
  • 第 3 部分介绍了 Nest 解决消息格式挑战的方法。接下来,我们将通过代码示例来演示如何实现该方法。本部分将使用之前介绍的词汇表,帮助您在解决这个问题时牢记一个简单的认知模型。
  • 第 4 部分(即将推出)涵盖一些剩余内容、一些高级用例以及一些技巧和窍门。

我希望您发现本系列文章很有帮助!

第一部分:鸟巢运输机简介

NestJS内置了一个面向网络通信的多功能子系统,称为(可能有点令人困惑)微服务。Nest 微服务的核心,以及理解它们的关键,是 Nest 所称的传输器。传输器使您能够使用可插拔通信层和非常简单的应用程序级消息协议通过网络连接组件。Nest 提供了各种开箱即用的传输器,以及允许开发人员构建新的自定义传输器的 API。这种架构和功能的组合非常强大。

Nest 内置传输器的一个具体示例是NATS。插入 NATS 风格的传输器后,Nest 应用可以使用 NATS 消息系统进行通信。由于 NATS 是这些通信的中介,Nest 不仅可以与其他 Nest 应用通信,还可以与非 Nest 应用通信,或者两者兼而有之。这引出了四个有趣的用例(如下图1)。

Transporter 用例

图 1:Transporter 用例

当前的Nest 微服务文档几乎完全关注第一个用例(案例 A)。确实如此。如果您只是想从强大的通信层中获益(例如,轻松添加负载平衡和容错功能以提高 Nest 应用程序的水平可扩展性),那么您真的不需要了解本系列文章中 90% 的内容!(不过,深入了解 Nest 微服务架构可能仍然对您有益,因此我建议您继续阅读。其中的大部分内容仍然是利用 Nest 微服务的相关背景知识)。

本系列文章的其余部分主要关注最后三个用例——即使用在 NATS 上运行的 Nest 微服务与外部系统集成。

为了实现这个目标,我们需要打下一些概念基础。让我们开始吧。

概念

即使从最高层次来看,该图也包含很多内容。一旦我们开始深入细节,繁杂的术语很容易妨碍我们更深入地理解。所以,事不宜迟,让我们来了解一些基本概念和术语。

首先,我们要知道 Nest 传输器主要有两种类型,我将其称为:

本文主要关注基于代理的传输器,因此我们将以 NATS 作为案例研究示例,进一步讨论该类型传输器。我们首先对代理进行一般性描述。已安装的代理由以下几个部分组成:

  • 代理服务器:这是一个主动服务器进程(或可能是复制的服务器),用于管理发布和订阅(以及随之而来的簿记),并将消息传递给客户端。
  • 代理客户端 API:它以特定语言的包(例如 JavaScript、Java、Go 等)的形式提供,提供用于从客户端程序访问代理的 API。客户端程序既包括您使用该 API 编写的“原生 NATS 应用程序”(无需框架),也包括像 NestJS 这样使用客户端 API 与代理通信的框架。

消息流图通常仅标记代理组件(例如,在我们的例子中,为位于其他各个组件中间的绿色圆圈),但将客户端 API 视为嵌入在其他框中,并将它们连接到代理。

基于代理的消息系统的主要优势在于,它能够将各个应用程序组件彼此解耦。每个组件只需连接到代理,而无需了解其他组件的存在、位置或实现细节。组件之间唯一需要共享的就是消息协议。

Nest 使用了大多数经纪商通常具备的一小部分功能。这样说可能有点过于笼统——我们稍后会再讨论——但现在,让我们先来看看 Nest 所依赖的常见经纪商功能。

代理消息协议

从 Nest 的角度来看,代理提供了一种基本的面向消息的通信协议,通常被描述为发布/订阅。发布/订阅可以通过下图来理解,其中红色圆圈表示发布/订阅“对话”中事件的顺序。

代理消息协议

图 2:代理消息协议

在该图中,每个客户端组件要么是发布者,要么是订阅者。关键在于,订阅者“注册对某个主题的兴趣”,而发布者发布关于某个主题的消息。代理位于中间,执行以下功能:

  • 跟踪订阅者(通过管理订阅列表
  • 接收已发布的消息
  • 将已发布的消息转发给所有感兴趣的订阅者(基于已发布消息和订阅之间的匹配主题)

这张图中明显缺少了请求/响应式通信模型。当我们想要验证已发布的消息是否收到,以及/或者接收消息使用者的响应时,这种通信模型非常有用。事实上,上图 1中隐含的消息模型就是这种(我们可以推断出'get-customers'是一个请求,而客户数组是一个响应)。那么,我们如何从发布/订阅模式过渡到请求/响应模式呢?

Nest(以及一些原生的代理,包括我们很快会看到的 NATS)通过在发布/订阅模型之上构建请求/响应功能,以一种便捷的方式解决了这个问题。假设组件 A 希望从组件 B 那里“获取客户”,而组件 B 可以访问客户数据库。组件 A 可以发布一条“获取客户”消息,并且(假设它已经订阅了该主题)组件 B 接收该消息,在客户数据库中查询客户列表,然后发送一条响应消息。响应消息就是奇迹发生的地方。为了让 B 响应 A,它们双方必须按照约定完成以下几件事:

  • A 选择回应主题(有时称为回复主题
  • A 订阅响应主题
  • A 将响应主题作为初始消息的一部分传递
  • B 使用响应主题作为自己后续响应消息的主题

换句话说,如果订阅者收到的消息中包含响应主题,则订阅者会在该响应主题上发布一条响应消息。因此,在我们的示例中,B 将其响应(包含包含客户列表的有效负载)发布到其在“请求”消息中收到的响应主题上。由于 A 先前已订阅该响应主题,因此它会将响应作为普通消息接收。一切都非常简洁!

Transporter 用例词汇表

考虑到这一点,我们可以为系统处理的两种不同类型的消息命名,并描述这些消息中参与者的角色。别担心,我们的准备工作快完成了!这些术语很快就会成为我们的第二天性,但事实证明,拥有一套词汇表非常有用,它能让我们快速浏览一堆看起来相似的数据流图,并跟踪我们的自定义代码的适用位置!

基于上述功能,我们可以交换的第一种消息称为事件。参与基于事件的消息传递的任何给定组件可以是以下任一类型:

  • 事件发射器- 意味着它会发布一条包含主题(以及可选的有效负载)的消息。事件发射器是一个纯粹的消息发布者。
  • 事件订阅者- 意味着它注册对某个主题的兴趣,并在发射器发布与该主题匹配的消息时接收消息(由代理转发)。

我们可以交换的第二种消息称为请求/响应消息。在这种交换中,参与组件可以是:

  • 请求者- 意味着它发布一条打算被视为请求的消息,并且它还采取上面描述的常规步骤 - 即订阅响应主题并将该响应主题包含在它发布的消息中。
  • 响应- 意味着它订阅一个它打算作为传入请求处理的主题,它产生一个结果,并且它将一个响应(包括结果有效负载)发布到它在入站请求上收到的响应主题。

因此,请求者是发布者的一个特例,我们用这个术语来提醒他们等待响应。响应者是订阅者的一个特例,我们用这个术语来提醒他们发布响应。明白了吗?如果没有,别担心,我们很快就会明白的。

集成用例

以上大部分内容都是在全 Nest 环境中“有效”的背景知识。现在,让我们将注意力转向使用 NATS 作为通信中介,将 Nest 应用与非 Nest 应用集成。

图 1中的案例 B、C 和 D 展示了基于 Nest 的应用使用 NATS 与非 Nest 应用集成的不同拓扑结构。请注意,我们将使用通用术语“应用”来描述这些通信组件。正如我们将看到的,这些“应用”可以是 Web 应用、用 NestJS 或其他平台编写的应用服务、“微服务”等等,但无论涉及哪种类型的应用,我们将描述的规则都是通用的。让我们简要介绍一下这些用例。

在方案 B 中,我们可能会将负责管理客户数据库的旧版应用服务迁移到 Nest。在这种情况下,我们的 Nest 应用需要扮演我们上面描述的响应者角色。今后,我们将此称为Nest 作为响应者的用例。

在案例 C 中,我们可能在 Nest 中编写一个新应用,该应用需要使用现有(遗留)服务查询客户数据库。在这种情况下,我们的 Nest 应用需要扮演我们上面描述的请求者角色。接下来,我们将此用例称为“ Nest 作为请求者”

在情况 D 中,我们可能正处于一个复杂的迁移过程中,两种类型的通信都在进行。Nest 应用需要能够通过 NATS 与其他 Nest 应用和非 Nest 应用通信。非 Nest 应用同样需要能够与这两种类型的应用通信。展望未来,我们将这种情况称为Nest 双工请求者/响应者用例。

我们将更详细地研究每个用例,包括实现此功能所需的示例代码(完整代码库及使用说明请点击此处)。在此之前,我们需要进一步了解 Nest 组件如何与代理交互。我们将在本系列的下一篇文章中介绍这一点。

欢迎在下方评论区提问、评论或建议,或者直接打个招呼。欢迎加入我们的Discord,一起愉快地讨论 NestJS。我在那里的用户名是Y Prospect

文章来源:https://dev.to/nestjs/integrate-nestjs-with-external-services-using-microservice-transporters-part-1-p3
PREV
使用 NestJS、Passport 和 Redis 设置会话
NEXT
使用 NestJS 实现 MVC 应用的身份验证和会话