NestJS 微服务示例
NestJS 微服务示例
项目生成
$ nest new nest-app -p yarn
该项目将作为主项目使用。
此时的目录结构为:
.
├── README.md
├── nest-cli.json
├── package.json
├── src
│ ├── app.controller.spec.ts
│ ├── app.controller.ts
│ ├── app.module.ts
│ ├── app.service.ts
│ └── main.ts
├── test
│ ├── app.e2e-spec.ts
│ └── jest-e2e.json
├── tsconfig.build.json
├── tsconfig.json
再生成另一个项目作为微服务项目:
$ cd nest-app
$ nest g app nest-service
此时的目录结构更新成了:
.
├── README.md
├── apps
│ ├── nest-app
│ │ ├── src
│ │ │ ├── app.controller.spec.ts
│ │ │ ├── app.controller.ts
│ │ │ ├── app.module.ts
│ │ │ ├── app.service.ts
│ │ │ └── main.ts
│ │ ├── test
│ │ │ ├── app.e2e-spec.ts
│ │ │ └── jest-e2e.json
│ │ └── tsconfig.app.json
│ └── nest-service
│ ├── src
│ │ ├── app.controller.spec.ts
│ │ ├── app.controller.ts
│ │ ├── app.module.ts
│ │ ├── app.service.ts
│ │ └── main.ts
│ ├── test
│ │ ├── app.e2e-spec.ts
│ │ └── jest-e2e.json
│ └── tsconfig.app.json
├── nest-cli.json
├── package.json
├── tsconfig.build.json
├── tsconfig.json
nest-cli.json
此时主程序 nest-app
和微服务 nest-service
同在一个仓库中,称为 monorepo
,注意到根目录有个 nest-cli.josn
文件,可以配置 monorepo 的参数。
比如其中 root
指定了哪个项目是主项目,也可通过 tsConfigPath
为每个项目指定自己的 tsconfig.json 文件路径等。关于 nest-cli.json 的详细配置参见文档。
{
"collection": "@nestjs/schematics",
"sourceRoot": "apps/nest-app/src",
"monorepo": true,
"root": "apps/nest-app",
"compilerOptions": {
"webpack": true,
"tsConfigPath": "apps/nest-app/tsconfig.app.json"
},
"projects": {
"nest-app": {
"type": "application",
"root": "apps/nest-app",
"entryFile": "main",
"sourceRoot": "apps/nest-app/src",
"compilerOptions": {
"tsConfigPath": "apps/nest-app/tsconfig.app.json"
}
},
"nest-service": {
"type": "application",
"root": "apps/nest-service",
"entryFile": "main",
"sourceRoot": "apps/nest-service/src",
"compilerOptions": {
"tsConfigPath": "apps/nest-service/tsconfig.app.json"
}
}
}
}
微服务开发
下面开始改造 nest-service
项目使其提供微服务被调用的能力。
添加微服务依赖
$ yarn add @nestjs/microservices
创建微服务
修改 nest-service/main.ts 中 NestFactory.create
为 NestFactory.createMicroservice
,后者用于创建一个微服务实例。它接收两个参数,第一个和正常创建 nest app 一样,另一个则用于控制要创建的微服务的具体属性,比如端口,地址等。
import { NestFactory } from '@nestjs/core';
import { MicroserviceOptions, Transport } from '@nestjs/microservices';
import { AppModule } from './app.module';
async function bootstrap() {
const app = await NestFactory.createMicroservice<MicroserviceOptions>(
AppModule,
{
transport: Transport.TCP,
options: {
port: 4000,
},
},
);
await app.listen(() => {
console.log(`nest service is listning`);
});
}
bootstrap();
添加消息处理器
apps/nest-service/src/app.controller.ts
import { Controller } from '@nestjs/common';
import { MessagePattern } from '@nestjs/microservices';
import { AppService } from './app.service';
@Controller()
export class AppController {
constructor(private readonly appService: AppService) {}
@MessagePattern({ cmd: 'getHello' })
getHello(name: string): string {
return this.appService.getHello(name);
}
}
这里 MessagePattern
定义了个消息处理器,它将监听并处理调用方发来的指令为 getHello
的消息。
其依赖的服务:
apps/nest-service/src/app.service.ts
import { Injectable } from '@nestjs/common';
@Injectable()
export class AppService {
getHello(name: string): string {
return `Hello ${name}!`;
}
}
修改启动命令
启动单个项目
nest start nest-app
nest start nest-service
开发过程中因为需要从主项目调用微服务提供的服务,通过 concurrently
来同时启动两个项目,
$ yarn add -D concurrently
"start:dev": "concurrently --kill-others \"nest start nest-app --watch\" \"nest start nest-service --watch\"",
$ yarn start:dev
调用
要调用微服务,需要先初始化一个客户端对象。因为 nest 支持多种类型的微服务,所以提供 ClientProxy 对象作为统一的客户端,完成初始化之后使用者无需关心不同类型微服务的差异,该代理对象对外提供了统一的调用接口。
有多种方式可初始这样的客户端。推荐下方第一种方式,在根模块中注册后,整个模块中通过依赖注入方式使用,高效便捷。而后面两种方式不易共享也难测试。
依赖注入
apps/nest-app/src/app.module.ts
import { Module } from '@nestjs/common';
import { ClientsModule, Transport } from '@nestjs/microservices';
import { AppController } from './app.controller';
import { AppService } from './app.service';
@Module({
imports: [
ClientsModule.register([
{
name: 'NEST_SERVICE',
transport: Transport.TCP,
options: {
port: 4000,
},
},
]),
],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}
使用时通过 @Inject('NEST_SERVICE')
进行注入。
apps/nest-app/src/app.service.ts
import { Inject, Injectable } from '@nestjs/common';
import { ClientProxy } from '@nestjs/microservices';
@Injectable()
export class AppService {
constructor(@Inject('NEST_SERVICE') private readonly client: ClientProxy) {}
getHello(name: string): Promise<string> {
return this.client.send<string>({ cmd: 'getHello' }, name).toPromise();
}
}
工厂方法
apps/nest-app/src/app.service.ts
import { Injectable } from '@nestjs/common';
import {
ClientProxy,
ClientProxyFactory,
Transport,
} from '@nestjs/microservices';
@Injectable()
export class AppService {
private client: ClientProxy;
constructor() {
this.client = ClientProxyFactory.create({
transport: Transport.TCP,
options: {
port: 4000,
},
});
}
getHello(name: string): Promise<string> {
return this.client.send<string>({ cmd: 'getHello' }, name).toPromise();
}
}
装饰器
apps/nest-app/src/app.service.ts
import { Injectable } from '@nestjs/common';
import { Client, ClientProxy, Transport } from '@nestjs/microservices';
@Injectable()
export class AppService {
@Client({ transport: Transport.TCP, options: { port: 4000 } })
private client: ClientProxy;
getHello(name: string): Promise<string> {
return this.client.send<string>({ cmd: 'getHello' }, name).toPromise();
}
}
测试
$ curl "localhost:3000?name=niuwayong"
Hello niuwayong!⏎