10 分钟内使用 NestJs 构建 API 网关 10 分钟内使用 NestJs 构建 API 网关

2025-06-07

10 分钟内使用 NestJs 构建 API 网关

10 分钟内使用 NestJs 构建 API 网关

10 分钟内使用 NestJs 构建 API 网关

本文旨在帮助您更广泛地了解微服务架构。许多人声称他们拥有面向微服务的架构,但却缺乏这种模式所依赖的核心概念。我的目标是撰写一系列文章,以扫清从单体应用向高度分布式应用转变过程中出现的所有迷雾。

微服务的世界充满了各种有趣且难以实现的东西。刚开始的时候,你会以为只要把应用分成多个服务就万事大吉了。可惜的是,这几乎从来都不是真的。人们在还没有掌握所有核心概念的情况下,就以这种方式构建高度关键的应用,这种情况比你想象的要常见得多。

在本文中,我将重点介绍API 网关模式。如果您正在开发微服务架构,那么您应该非常了解它,因为本文的目的是确保您对这些概念有清晰的了解。如果您对微服务完全陌生,那就尽情享受吧,享受学习的乐趣。

在传统的单体应用中,API 客户端会从同一位置使用所有内容。然而,一旦开始使用微服务,情况就会发生变化。您可能会有多个服务运行在完全不同的位置。

API 网关的含义

微服务架构的不确定性直接导致了全新的混乱。但你能做些什么呢?其中一种方法是使用 API 网关。从 10,000 英尺的高度来看,它只是放置在其他服务之前的一项额外服务,以便你能够组合服务。

问题

假设你有一个包含多个服务的应用程序。我们希望对客户端隐藏服务的位置,因此我们需要一个能够组合多个请求的代理服务。

解决方案

我们将使用 NestJs。如果你还没用过,你应该知道它和 Angular 非常相似,而且我认为它是一种让前端开发者也能在后端进行操作的巧妙方法。总之,它附带一个允许代码生成的 CLI 工具。

如果你需要它

假设您了解 NestJs,或者您已经阅读了我刚刚提供的文章,那么让我们开始编写代码吧。但在开始之前,您需要通过执行以下命令全局安装 NestJs CLI npm install -g @nestjs/cli

创建第一个服务

在任何微服务架构中,你都会发现多个服务正在运行,它们要么在同一台机器上,要么在完全分布式的地方。为了开始我们的小规模概念验证,我们将使用 NestJs CLI 创建一个服务。只需按照以下步骤操作:

  1. 创建一个新文件夹,然后使用您喜欢的命令行工具转到该文件夹​​。
  2. 执行nest new service-a。它会提示你在 npm 和 yarn 之间进行选择。我选择了 npm。
  3. 删除文件src/app.controller.spec.tssrc/app.service.ts
  4. AppService从中删除用法AppModule
  5. AppService从中删除用法AppController

最终AppModule结果如下:

// src/app.module.ts
import { Module } from "@nestjs/common";
import { AppController } from "./app.controller";

@Module({
  imports: [],
  controllers: [AppController],
  providers: []
})
export class AppModule {}
Enter fullscreen mode Exit fullscreen mode

最终AppController结果如下:

import { Controller, Get } from "@nestjs/common";

@Controller()
export class AppController {
  @Get()
  getHello(): string {
    return "hello";
  }
}
Enter fullscreen mode Exit fullscreen mode

您已经拥有了自己的第一个服务!现在是时候将其转换为微服务了。值得庆幸的是,NestJs 为您涵盖了其中的大部分内容。默认情况下,NestJs 应用程序会生成为使用 HTTP 作为传输层的服务器。对于微服务来说,这并非您想要的。在使用微服务时,您通常会使用 TCP。

如果你对 HTTP 或 TCP 感到困惑,可以想象它们只是语言而已。传统的 Http 服务器使用英语进行通信,而使用 TCP 的微服务使用西班牙语进行通信。

由于该服务在结构上已准备好使用 NestJs 转换为微服务,因此我们将首先执行以下步骤:

  1. 使用您首选的命令行工具转到服务文件夹
  2. 执行命令npm i --save @nestjs/microservices
  3. src/main.ts使用服务配置更新服务的入口点
  4. 更新AppController以使用微服务消息模式为客户端提供服务

入口点最终看起来应该是这样的:

import { NestFactory } from "@nestjs/core";
import { Transport } from "@nestjs/microservices";
import { AppModule } from "./app.module";
import { Logger } from "@nestjs/common";

const logger = new Logger();

async function bootstrap() {
  const app = await NestFactory.createMicroservice(AppModule, {
    transport: Transport.TCP,
    options: {
      host: "127.0.0.1",
      port: 8888
    }
  });
  app.listen(() => logger.log("Microservice A is listening"));
}
bootstrap();
Enter fullscreen mode Exit fullscreen mode

你是不是好奇这到底是怎么回事?让我来解释一下。

  1. 我们正在使用createMicroservice而不是默认的create
  2. 现在我们必须为传输和微服务选项提供额外的参数。
  3. 在微服务选项中,我们告诉 NestJs 我们想要使用的主机和端口。

注意:您可以选择您喜欢的主机和端口。此外,NestJs 有多种传输选项可供选择。

最终AppController结果如下:

import { Controller } from "@nestjs/common";
import { MessagePattern } from "@nestjs/microservices";
import { of } from "rxjs";
import { delay } from "rxjs/operators";

@Controller()
export class AppController {
  @MessagePattern({ cmd: "ping" })
  ping(_: any) {
    return of("pong").pipe(delay(1000));
  }
}
Enter fullscreen mode Exit fullscreen mode

我们没有使用经典的装饰器,而是Get使用了MessagePattern。它的作用是ping在收到ping命令时触发该方法。然后,它会在一秒的延迟后返回字符串pong 。

如果您想跳过此步骤,您可以访问创建第一个服务的工作版本

构建 API 网关

您需要运行一个新服务,但如何访问它呢?这就是我们接下来要做的。我们将创建一个新的服务,用作 HTTP 服务器,并将请求映射到正确的服务。它看起来像一个代理,允许您编写请求,并减少应用程序的带宽占用。

如果你想知道谁会使用它,AWS 将其作为 SaaS 提供。Netflix 甚至构建了自己的解决方案。

让我们利用您对 NestJs CLI 的了解:

  1. service-a使用您喜欢的命令行工具转到项目所在的目录。
  2. 执行nest new api-gateway。它会提示你在 npm 和 yarn 之间进行选择。我选择了 npm。
  3. 删除文件src/app.controller.spec.ts

你可能会想,就这样了吗?嗯,不是。不过我们快完成了。现在是时候hook我们创建的方法了。

  1. 使用您喜欢的命令行工具转到 API 网关根文件夹。
  2. 执行命令npm i --save @nestjs/microservices
  3. 导入ClientModule并注册ServiceA
  4. 将新服务注入AppService并创建查询的方法ServiceA
  5. AppService使用中的新方法AppController

最终AppModule结果如下:

import { Module } from "@nestjs/common";
import { AppController } from "./app.controller";
import { ClientsModule, Transport } from "@nestjs/microservices";
import { AppService } from "./app.service";

@Module({
  imports: [
    ClientsModule.register([
      {
        name: "SERVICE_A",
        transport: Transport.TCP,
        options: {
          host: "127.0.0.1",
          port: 8888
        }
      }
    ])
  ],
  controllers: [AppController],
  providers: [AppService]
})
export class AppModule {}
Enter fullscreen mode Exit fullscreen mode

如您所见,我们需要使用相同的传输和选项来设置客户端,但需要为其添加一个新属性name来标识服务的实例。您还可以创建自定义提供程序,以便从本地或通过 HTTP 进行外部访问的服务中获取其配置。

最终AppService结果如下:

import { Injectable, Inject } from "@nestjs/common";
import { ClientProxy } from "@nestjs/microservices";
import { map } from "rxjs/operators";

@Injectable()
export class AppService {
  constructor(
    @Inject("SERVICE_A") private readonly clientServiceA: ClientProxy
  ) {}

  pingServiceA() {
    const startTs = Date.now();
    const pattern = { cmd: "ping" };
    const payload = {};
    return this.clientServiceA
      .send<string>(pattern, payload)
      .pipe(
        map((message: string) => ({ message, duration: Date.now() - startTs }))
      );
  }
}
Enter fullscreen mode Exit fullscreen mode

我们在这里所做的是注入我们导入的客户端,AppModule并使用其名称作为令牌来标识它。然后,我们创建一个简单的方法,以毫秒为单位获取当前时间,向服务实例发送一条消息,一旦收到响应,就将其映射到一个包含响应消息及其总时长的对象。

最终AppController结果如下:

import { Controller, Get } from "@nestjs/common";
import { AppService } from "./app.service";

@Controller()
export class AppController {
  constructor(private readonly appService: AppService) {}

  @Get("/ping-a")
  pingServiceA() {
    return this.appService.pingServiceA();
  }
}
Enter fullscreen mode Exit fullscreen mode

如果您使用 启动api-gatewayservice-a服务npm run start:dev,您将能够通过调用http://localhost:3000/ping-a向 API 网关发送 GET 请求,并获取响应,其中包含一条消息,指出pong及其花费的持续时间。

虽然这没什么特别的,对吧?我们用一个简单的代理就能搞定。但如果要组合请求,事情会稍微复杂一些。不过在此之前,我们需要创建一个新的服务。接下来,创建第二个服务,并像我刚才演示的那样,将其挂接到 API 网关上。

如果您想跳过前面的步骤,您可以使用一个服务访问 api 网关,或者使用两个服务访问 api 网关

注意:在第二个服务中,我使用了 2 秒的延迟,以便我们可以看到可用服务之间的差异。

编写请求

一切就绪——两个可以在任何地方运行的服务通过单一接口进行通信,为应用程序带来了更高的安全性和模块化。但我们想要更多。如果我们有 12 个服务,并且需要发出 100 多个请求才能将所有信息填充到一个页面中,该怎么办?情况会变得难以控制。

我们需要一种在 API 网关中编写请求的方法。为此,我将使用一些 RxJs。APIAppController网关最终将如下所示:

import { Controller, Get } from "@nestjs/common";
import { AppService } from "./app.service";
import { zip } from "rxjs";
import { map } from "rxjs/operators";

@Controller()
export class AppController {
  constructor(private readonly appService: AppService) {}

  @Get("/ping-a")
  pingServiceA() {
    return this.appService.pingServiceA();
  }

  @Get("/ping-b")
  pingServiceB() {
    return this.appService.pingServiceB();
  }

  @Get("/ping-all")
  pingAll() {
    return zip(
      this.appService.pingServiceA(),
      this.appService.pingServiceB()
    ).pipe(
      map(([pongServiceA, pongServiceB]) => ({
        pongServiceA,
        pongServiceB
      }))
    );
  }
}
Enter fullscreen mode Exit fullscreen mode

唯一新的东西是pingAll方法。如果你以前没见过 RxJs,这可能看起来像某种黑魔法,但实际上非常简单。我们希望同时启动异步调用的执行,并将所有响应合并为一个。

注意:zip 方法接受N 个可观察对象,并在所有可观察对象都发出后才发出。

如果您不想自己做任何事,只需访问该应用程序的此工作版本

结论

就这样,您就拥有了 API 网关来为您编写请求。这只是微服务为您的架构带来诸多功能的小小尝试。您还可以探索更多模式,例如 API 网关。一个很棒的家庭作业是创建一个新的服务来跟踪正在运行的服务,并使用提供程序扩展导入,以便动态设置客户端规范。

文章来源:https://dev.to/thisdotmedia/build-an-api-gateway-with-nestjs-in-10-minutes-16db
PREV
如何向 PWA 添加通知
NEXT
企业中的 Angular 开发