本文介绍了一种简单的调用微服务方式,适用于快速开发nestjs微服务模块
nestjs的微服务可以类似于springboot,你可以启动多个app分别监听不同的端口
比如localhost:3000是微服务1,localhost:3001是微服务2,每个app都是通过NestFactory.create创建的
但是有的微服务模块供内部调用,而直接通过post和get请求是无法直接调用此模块的,这种模块就是内部调用的微服务模块
具体文档参考nestjs官网 Documentation | NestJS - A progressive Node.js framework
本文对nestjs官网所说的不全面的地方,以及我踩坑的地方详细说明
首先我们使用nest-cli创建一个新项目
npm i -g @nestjs/cli
nest new yourprojectname
接着选择你喜欢使用的包管理工具,在这里我选择使用npm
创建的文件中,打开src/main.ts,你可以看到如下代码
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
await app.listen(3000);
}
bootstrap();
然后我们要创建一个微服务,打开控制台,输入下方代码后,使用你喜欢的包管理器
nest new microservice1
我们创建了一个微服务,然后打开这个文件夹,进入microservice1/src/main.ts,修改为如下代码,
下面解释一下,NestFactory.createMicroservice需要传递两个参数,第一个参数是模块名称,通常是主app模块,第二个参数是一个option对象,这个对象里包含transport属性,指定了此微服务模块通过什么方式通信,第二个属性options指定了微服务模块的host,port等信息,因为本次都是在localhost上,所以只指定了端口
import { NestFactory } from '@nestjs/core';
import { Transport, MicroserviceOptions } from '@nestjs/microservices';
import { AppModule } from './app.module';
async function bootstrap() {
const app = await NestFactory.createMicroservice<MicroserviceOptions>(
AppModule,
{
transport: Transport.TCP,
options:{
port:3001,
}
},
);
app.listen();
}
bootstrap();
这里声明了一个内部微服务模块,其端口是3001,注意不要跟主app和其他微服务端口冲突!使用TCP进行通信,注意,微服务模块不能直接使用get和post请求,而是使用了'pattern'的方式来进行与其他微服务模块之间的相互调用,每个微服务接口都有一个pattern,其他微服务通过触发这个pattern来进行通信,pattern有两种@MessagePattern和@EventPattern,都是在@nestjs/microservices包中导入的,MessagePattern通常用于请求-响应,而EventPattern常常用于接收事件,在本次演示中,我们需要响应请求,所以使用了@MessagePattern
如下代码在microservice1/src/app.controller.ts,我们创建了一个简单的微服务接口,注意我们需要在当前的microservice1模块中安装@nestjs/microservices依赖
import { Controller, Get } from '@nestjs/common';
import { AppService } from './app.service';
import { MessagePattern } from '@nestjs/microservices';
@Controller()
export class AppController {
constructor(private readonly appService: AppService) {}
@MessagePattern('math:wordcount')
wordCount(s: string): string {
console.log('hello world')
return '这是math微服务!';
}
}
接下来我们可以启动这个微服务了!将控制台目录进入到microservice1的目录下,输入
npm run start:dev
启动我们的微服务模块,但是注意这个微服务模块还不能通过postman直接调用
然后我们在我们的主app应用中注册这个微服务,打开外面的yourprojectname/src/app.module.ts,
注意安装@nestjs/microservices包后,通过ClientsModule.register来注册一个或者多个微服务,其参数是一个数组,在每个对象中配置微服务的名称,微服务的通信方式(本次使用TCP),微服务的端口号,这样在我们的主app中我们就可以找到这个微服务并进行通信了!如果微服务模块不在本机上,还需要指定微服务模块的host,这样才能准确定位微服务模块的位置并成功与其通信
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { ClientsModule, Transport } from '@nestjs/microservices'; // 注册一个用于对微服务进行数据传输的客户端
@Module({
imports: [
ClientsModule.register([
{
name: 'MATH_SERVICE',
transport: Transport.TCP,
options: {
port: 3001,
},
},
]),
],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}
我们通过依赖注入的方式来使用微服务,在yourprojectname/src/app.controller.ts
在其构造函数中注入微服务客户端,这里通过我们注册微服务的名称来选择使用哪个微服务客户端,然后通过这个对象来进行与微服务的通信
import { Controller, Get, Inject } from '@nestjs/common';
import { AppService } from './app.service';
import { ClientProxy } from '@nestjs/microservices';
import { Observable } from 'rxjs';
@Controller()
export class AppController {
constructor(
private readonly appService: AppService,
@Inject('MATH_SERVICE') private client: ClientProxy,
) {}
@Get('/ms')
getHello(): any{
console.log('调用微服务!')
return this.client.send('math:wordcount', '');
}
}
这里我们有两种方式与微服务通信,this.client.send和this.client.emit,前者是通过请求-响应的方式,后者是通过事件驱动的方式,这两个的区别是,当我们需要有响应的时候,我们采用send方法,其对应微服务controller中的@MessagePattern,而emit方法表示我们要触发什么事件,对应@EventPattern,而后者通常不需要响应的返回值,只起到通知的作用.
好了,现在我们已经全部定义完了,启动我们的主服务,在根工作目录下输入如下命令:
npm run start:dev
下面打开postman,使用get请求localhost:3000/ms,我们可以得到响应:这是math微服务!
总结一下nestjs的微服务创建流程:
1.创建主app模块
2.创建微服务模块,修改其main.ts,让app成为被createMicroservice创建的,注意端口不要冲突
3.在主app中的module中注册子微服务
4.在主app中的controller或者service中依赖注入微服务客户端,使用send(请求响应)和emit(事件发布)与微服务的controller进行通信
加餐:在上面第3和第4是不是很麻烦,又要在主应用的module中注册,然后才能在service或者controller中注入才行,有一个简单的办法!
以前我们都是使用构造函数注入的,我们可以有一种更简单的注入方式!
我们可以直接通过@Client装饰器注入,这个装饰器填的内容跟在module中使用ClientsModule.register注册的一模一样!,这样我们就不用写在module中了,这样的好处就是方便使用,当我们微服务模块比较少的时候,这样写非常简单,但是注意!当我们微服务模块很多的时候,官方并不推荐我们使用这样的方法,因为它更难测试,也更难共享客户端实例,只有在微服务模块比较少的时候才会使用这种方法,而当我们微服务模块比较多的时候,推荐使用写在module中的imports中使用ClientsModule.register统一注册管理!
import { Controller, Get, Inject } from '@nestjs/common';
import { AppService } from './app.service';
import { ClientProxy, Client, Transport } from '@nestjs/microservices';
import { Observable } from 'rxjs';
@Controller()
export class AppController {
@Client({ transport: Transport.TCP, options: { port: 3001 } })
private client: ClientProxy;
constructor(
private readonly appService: AppService,
) // @Inject('MATH_SERVICE') private client: ClientProxy,
{}
@Get('/ms')
getHello():Observable<any> {
console.log('调用math微服务!');
return this.client.send('math:wordcount', '');
}
}
后续将会更新:Nestjs微服务项目已经开发完毕了,那么应该怎么上线呢?我们将使用docker来将微服务通过dockerfile打包成一个镜像,然后通过docker-compose的方式进行项目部署