微服务

微服务

基本

Nest 微服务是一种使用与HTTP不同的传输层的应用程序。

微服务 - 图1

安装

首先,我们需要安装所需的软件包:

$ npm i --save @nestjs/microservices

概述

通常,Nest支持一系列内置的传输器。它们基于 请求-响应 范式,整个通信逻辑隐藏在抽象层之后。多亏了这一点,您可以轻松地在传输器之间切换,而无需更改任何代码行。我们不支持具有基于日志的持久性的流平台,例如 Kafka或 NATS流,因为它们是为解决不同范围的问题而创建的。但是,您仍然可以通过使用执行上下文功能将它们与Nest一起使用。

为了创建微服务,我们使用 NestFactory 类的 createMicroservice() 方法。默认情况下,微服务通过 TCP协议 监听消息。

main.ts

import { NestFactory } from '@nestjs/core';
import { Transport } from '@nestjs/microservices';
import { ApplicationModule } from './app.module';

async function bootstrap() {
  const app = await NestFactory.createMicroservice(ApplicationModule, {
    transport: Transport.TCP,
  });
  app.listen(() => console.log('Microservice is listening'));
}
bootstrap();

createMicroservice () 方法的第二个参数是options对象。此对象可能有两个成员:

| ---|--- transport | 指定传输器 options | 确定传输器行为的传输器特定选项对象

options 对象根据所选的传送器而不同。TCP传输器暴露了下面描述的几个属性。

| ---|--- host | 连接主机名 port | 连接端口 retryAttempts | 连接尝试的总数 retryDelay | 连接重试延迟(ms)

模式(patterns)

微服务通过 模式 识别消息。模式是一个普通值,例如对象、字符串或甚至数字。为了创建模式处理程序,我们使用从 [@nestjs](https://github.com/nestjs) "@nestjs")/microservices 包导入的 [@MessagePattern](https://github.com/MessagePattern) "@MessagePattern")() 装饰器。

math.controller.ts

import { Controller } from '@nestjs/common';
import { MessagePattern } from '@nestjs/microservices';

@Controller()
export class MathController {
  @MessagePattern({ cmd: 'sum' })
  sum(data: number[]): number {
    return (data || []).reduce((a, b) => a + b);
  }
}

sum () 处理程序正在监听符合 cmd :'sum' 模式的消息。模式处理程序采用单个参数,即从客户端传递的 data 。在这种情况下,数据是必须累加的数字数组。

异步响应

每个模式处理程序都能够同步或异步响应。因此,支持 async (异步)方法。

math.controller.ts

@MessagePattern({ cmd: 'sum' })
async sum(data: number[]): Promise<number> {
  return (data || []).reduce((a, b) => a + b);
}

此外,我们能够返回 RxJS Observable,因此这些值将被发出,直到流完成。

math.controller.ts

@MessagePattern({ cmd: 'sum' })
sum(data: number[]): Observable<number> {
  return from([1, 2, 3]);
}

以上消息处理程序将响应3次(对数组中的每个项)。

客户端

为了连接 Nest 的微服务,我们使用 ClientProxy 类,该类实例通过 [@Client](https://github.com/Client) "@Client")() 装饰器分配给属性。这个装饰器只需要一个参数。即与 Nest 微服务选项对象相同的对象。

@Client({ transport: Transport.TCP })
client: ClientProxy;

!> [@Client](https://github.com/Client) "@Client")() 和 ClientProxy 需要引入 [@nestjs](https://github.com/nestjs) "@nestjs")/microservices 。

ClientProxy 公开了一个 send() 方法。此方法旨在调用微服务并将其响应返回给 Observable,这意味着,我们可以轻松订阅发送的值。

@Get()
call(): Observable<number> {
  const pattern = { cmd: 'sum' };
  const data = [1, 2, 3, 4, 5];

  return this.client.send<number>(pattern, data);
}

send() 方法接受两个参数,pattern 和 data。pattern 必须与 [@MessagePattern](https://github.com/MessagePattern) "@MessagePattern")() 装饰器中定义的模式相同,而 data 是要传输到另一个微服务的消息。

Redis

第二个内置传输器基于 Redis 数据库。此传输器利用发布/订阅功能。

微服务 - 图2

安装

在开始之前,我们必须安装所需的软件包:

$ npm i --save redis

概述

为了从TCP传输策略切换到Redis pub/sub ,我们需要更改传递给 createMicroservice() 方法的选项对象。

main.ts

const app = await NestFactory.createMicroservice(ApplicationModule, {
  transport: Transport.REDIS,
  options: {
    url: 'redis://localhost:6379',
  },
});

选项

有许多可用的选项可以确定传输器的行为。

| ---|--- url | 连接网址 retryAttempts | 连接尝试的总数 retryDelay | 连接重试延迟(ms)

NATS

NATS 是一个简单、高性能的开源消息传递系统。

安装

在开始之前,我们必须安装所需的软件包:

$ npm i --save nats

概述

为了切换到 NATS 传输器,我们需要修改传递到 createMicroservice() 方法的选项对象。

main.ts

const app = await NestFactory.createMicroservice(ApplicationModule, {
  transport: Transport.NATS,
  options: {
    url: 'nats://localhost:4222',
  },
});

选项

有许多可用的选项可以确定传输器的行为。它们在 这里 有很好的描述。

gRPC

gRPC 是一个高性能、开源的通用RPC框架。

安装

在开始之前,我们必须安装所需的软件包:

$ npm i --save grpc

传输器

为了切换到 gRPC 传输器,我们需要修改传递到 createMicroservice() 方法的选项对象。

main.ts

const app = await NestFactory.createMicroservice(ApplicationModule, {
  transport: Transport.GRPC,
  options: {
    package: 'hero',
    protoPath: join(__dirname, './hero/hero.proto'),
  },
});

!> join()函数是从 path 包导入的。

选项

| ---|--- url | 连接网址 protoPath | .proto 文件的绝对路径 package | protobuf 包名 credentials | 服务器证书(阅读更多)

概述

通常,package 属性设置 protobuf 包名称,而 protoPath 是 .proto 文件的路径。hero.proto 文件是使用协议缓冲区语言构建的。

hero.proto

syntax = "proto3";

package hero;

service HeroService {
  rpc FindOne (HeroById) returns (Hero) {}
}

message HeroById {
  int32 id = 1;
}

message Hero {
  int32 id = 1;
  string name = 2;
}

在上面的示例中,我们定义了一个 HeroService,它暴露了一个 FindOne() gRPC处理程序,该处理程序期望 HeroById 作为输入并返回一个 Hero 消息。为了定义一个能够实现这个protobuf定义的处理程序,我们必须使用 [@GrpcRoute](https://github.com/GrpcRoute) "@GrpcRoute")() 装饰器。之前的 [@MessagePattern](https://github.com/MessagePattern) "@MessagePattern")() 将不再有用。

hero.controller.ts

@GrpcRoute('HeroService', 'FindOne')
findOne(data: HeroById, metadata: any): Hero {
  const items = [{ id: 1, name: 'John' }, { id: 2, name: 'Doe' }];
  return items.find(({ id }) => id === data.id);
}

!> [@GrpcRoute](https://github.com/GrpcRoute) "@GrpcRoute")() 需要引入 [@nestjs](https://github.com/nestjs) "@nestjs")/microservices 。

HeroService 是服务的名称,而 FindOne 指向 FindOne() gRPC处理程序。对应的 findOne() 方法接受两个参数,即从调用方传递的 data 和存储gRPC请求元数据的 metadata。

客户端

为了创建客户端实例,我们需要使用 [@Client](https://github.com/Client) "@Client")() 装饰器。

hero.controller.ts

@Client({
  transport: Transport.GRPC,
  options: {
    package: 'hero',
    protoPath: join(__dirname, './hero/hero.proto'),
  },
})
private readonly client: ClientGrpc;

与前面的例子相比有一点差别。我们使用提供 getService() 方法的 ClientGrpc,而不是 ClientProxy 类。getService() 泛型方法将服务的名称作为参数,并返回其实例(如果可用)。

hero.controller.ts

onModuleInit() {
  this.heroService = this.client.getService<HeroService>('HeroService');
}

heroService 对象暴露了 .proto 文件中定义的同一组方法。注意,所有这些都是 小写 (为了遵循自然惯例)。基本上,我们的gRPC HeroService 定义包含 FindOne() 函数。这意味着 heroService 实例将提供 findOne() 方法。

interface HeroService {
  findOne(data: { id: number }): Observable<any>;
}

所有服务的方法都返回 Observable。由于 Nest 支持 RxJS 流并且与它们很好地协作,所以我们也可以在HTTP处理程序中返回它们。

hero.controller.ts

@Get()
call(): Observable<any> {
  return this.heroService.findOne({ id: 1 });
}

这里 提供了一个完整的示例。

异常过滤器 (Exception filters)

HTTP异常过滤器层和相应的微服务层之间的唯一区别在于,不要抛出 HttpException,而应该使用 RpcException。

?> RpcException 类是从 [@nestjs](https://github.com/nestjs) "@nestjs")/microservices 包导入的。

Nest将处理引发的异常,并因此返回具有以下结构的 error 对象:

{
  status: 'error',
  message: 'Invalid credentials.'
}

过滤器

异常过滤器 的工作方式与主过滤器相同,只有一个小的区别。catch() 方法必须返回一个 Observable。

rpc-exception.filter.ts

import { Catch, RpcExceptionFilter, ArgumentsHost } from '@nestjs/common';
import { Observable, throwError } from 'rxjs';
import { RpcException } from '@nestjs/microservices';

@Catch(RpcException)
export class ExceptionFilter implements RpcExceptionFilter {
  catch(exception: RpcException, host: ArgumentsHost): Observable<any> {
    return throwError(exception.getError());
  }
}

!> 不能设置全局的微服务异常过滤器。

下面是一个使用手动实例化 方法作用域 过滤器(也可以使用类作用域)的示例:

@UseFilters(new ExceptionFilter())
@MessagePattern({ cmd: 'sum' })
sum(data: number[]): number {
  return (data || []).reduce((a, b) => a + b);
}

管道 (Pipes)

微服务管道和普通管道没有区别。唯一需要注意的是,不要抛出 HttpException ,而应该使用 RpcException。

?> RpcException 类是从 [@nestjs](https://github.com/nestjs) "@nestjs")/microservices 包导入的。

下面是一个使用手动实例化 方法作用域 管道(也可以使用类作用域)的示例:

@UsePipes(new ValidationPipe())
@MessagePattern({ cmd: 'sum' })
sum(data: number[]): number {
  return (data || []).reduce((a, b) => a + b);
}

看守器 (Guards)

微服看守器和普通看守器没有区别。唯一需要注意的是,不要抛出 HttpException ,而应该使用 RpcException。

?> RpcException 类是从 [@nestjs](https://github.com/nestjs) "@nestjs")/microservices 包导入的。

下面是一个使用 方法作用域 看守器(也可以使用类作用域)的示例:

@UseGuards(AuthGuard)
@MessagePattern({ cmd: 'sum' })
sum(data: number[]): number {
  return (data || []).reduce((a, b) => a + b);
}

拦截器 (Interceptors)

常规拦截器和微服务拦截器之间没有区别。下面是一个使用手动实例化 方法作用域 拦截器(也可以使用类作用域)的示例:

@UseInterceptors(new TransformInterceptor())
@MessagePattern({ cmd: 'sum' })
sum(data: number[]): number {
  return (data || []).reduce((a, b) => a + b);
}