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 创建一个服务。只需按照以下步骤操作:
- 创建一个新文件夹,然后使用您喜欢的命令行工具转到该文件夹。
- 执行
nest new service-a
。它会提示你在 npm 和 yarn 之间进行选择。我选择了 npm。 - 删除文件
src/app.controller.spec.ts
和src/app.service.ts
。 AppService
从中删除用法AppModule
。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 {}
最终AppController
结果如下:
import { Controller, Get } from "@nestjs/common";
@Controller()
export class AppController {
@Get()
getHello(): string {
return "hello";
}
}
您已经拥有了自己的第一个服务!现在是时候将其转换为微服务了。值得庆幸的是,NestJs 为您涵盖了其中的大部分内容。默认情况下,NestJs 应用程序会生成为使用 HTTP 作为传输层的服务器。对于微服务来说,这并非您想要的。在使用微服务时,您通常会使用 TCP。
如果你对 HTTP 或 TCP 感到困惑,可以想象它们只是语言而已。传统的 Http 服务器使用英语进行通信,而使用 TCP 的微服务使用西班牙语进行通信。
由于该服务在结构上已准备好使用 NestJs 转换为微服务,因此我们将首先执行以下步骤:
- 使用您首选的命令行工具转到服务文件夹
- 执行命令
npm i --save @nestjs/microservices
src/main.ts
使用服务配置更新服务的入口点- 更新
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();
你是不是好奇这到底是怎么回事?让我来解释一下。
- 我们正在使用
createMicroservice
而不是默认的create
。 - 现在我们必须为传输和微服务选项提供额外的参数。
- 在微服务选项中,我们告诉 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));
}
}
我们没有使用经典的装饰器,而是Get
使用了MessagePattern
。它的作用是ping
在收到ping命令时触发该方法。然后,它会在一秒的延迟后返回字符串pong 。
如果您想跳过此步骤,您可以访问创建第一个服务的工作版本。
构建 API 网关
您需要运行一个新服务,但如何访问它呢?这就是我们接下来要做的。我们将创建一个新的服务,用作 HTTP 服务器,并将请求映射到正确的服务。它看起来像一个代理,允许您编写请求,并减少应用程序的带宽占用。
如果你想知道谁会使用它,AWS 将其作为 SaaS 提供。Netflix 甚至构建了自己的解决方案。
让我们利用您对 NestJs CLI 的了解:
service-a
使用您喜欢的命令行工具转到项目所在的目录。- 执行
nest new api-gateway
。它会提示你在 npm 和 yarn 之间进行选择。我选择了 npm。 - 删除文件
src/app.controller.spec.ts
。
你可能会想,就这样了吗?嗯,不是。不过我们快完成了。现在是时候hook我们创建的方法了。
- 使用您喜欢的命令行工具转到 API 网关根文件夹。
- 执行命令
npm i --save @nestjs/microservices
。 - 导入
ClientModule
并注册ServiceA
。 - 将新服务注入
AppService
并创建查询的方法ServiceA
。 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 {}
如您所见,我们需要使用相同的传输和选项来设置客户端,但需要为其添加一个新属性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 }))
);
}
}
我们在这里所做的是注入我们导入的客户端,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();
}
}
如果您使用 启动api-gateway
和service-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
}))
);
}
}
唯一新的东西是pingAll
方法。如果你以前没见过 RxJs,这可能看起来像某种黑魔法,但实际上非常简单。我们希望同时启动异步调用的执行,并将所有响应合并为一个。
注意:zip 方法接受N 个可观察对象,并在所有可观察对象都发出后才发出。
如果您不想自己做任何事,只需访问该应用程序的此工作版本。
结论
就这样,您就拥有了 API 网关来为您编写请求。这只是微服务为您的架构带来诸多功能的小小尝试。您还可以探索更多模式,例如 API 网关。一个很棒的家庭作业是创建一个新的服务来跟踪正在运行的服务,并使用提供程序扩展导入,以便动态设置客户端规范。
文章来源:https://dev.to/thisdotmedia/build-an-api-gateway-with-nestjs-in-10-minutes-16db