Node版Spring - 那些让人眼前一亮的NestJS特性

2021-07-08 10:34:17 浏览数 (1)

点击上方 程序员成长指北,关注公众号

回复1,加入高级Node交流群

Nestjs的哲学:完全支持Typescript并解决架构问题,在服务器端提供开箱即用的应用架构,让开发人员和团队能够创造出高可测试、可扩展、松耦合、易维护的应用。

本文主要谈及一些和其他node框架稍微差异的特性,比如依赖注入控制器管道拦截器模块微服务

一、依赖注入

Provides是Nest的最基本的一个概念,许多基本的Nest类可能视为provider-service,repository,helper等等,在实际开发中,比如常用的service, repository。有了依赖注入我们能够提高应用程序的灵活性和模块化程度。举个例子说明:

代码语言:javascript复制
/* cats.service.ts */

import { Injectable } from '@nestjs/common';
import { Cat } from './interfaces/cat.interface';

@Injectable()
export class CatsService {
  private readonly cats: Cat[] = [];

  create(cat: Cat) {
    this.cats.push(cat);
  }

  findAll(): Cat[] {
    return this.cats;
  }
}

CatsService是具有一个属性和两个方法的基本类,和其他普通稍微差异的就是使用@Injectable()装饰器,通过该装饰器使Nest知道这个类是一个provider,现在我们使用类构造函数注入该服务:

代码语言:javascript复制
/* cats.controller.ts */

import { Controller, Get, Post, Body } from '@nestjs/common';
import { CreateCatDto } from './dto/create-cat.dto';
import { CatsService } from './cats.service';
import { Cat } from './interfaces/cat.interface';

@Controller('cats')
export class CatsController {
  constructor(private catsService: CatsService) {}

  @Post()
  async create(@Body() createCatDto: CreateCatDto) {
    this.catsService.create(createCatDto);
  }

  @Get()
  async findAll(): Promise<Cat[]> {
    return this.catsService.findAll();
  }
}

从上面代码来看, 我们在Controller里使用CatsService不是通过使用New来实例化, 而是在constuctor声明即可。

从上面可看出依赖注入有两个比较大的优势:

  1. 依赖管理交给Nest运行时系统
  2. 依赖项只关注类型不关注具体实例具有高度解耦性

二、控制器

控制器负责处理传入的请求和向客户端返回相应。

Controllers_1

一般的node框架可能没有控制器这个概念或者是等价路由概念,这里控制器相当于是路由资源集合。下图是一次请求生命周期:

pasted-from-clipboard

从图上可以看出请求会先走Middleware->Guards(守卫)->Interceptors(拦截器)->Pipes(管道)后才到达Controller, 那么接下来会讲解下管道和拦截器的概念。

三、管道&拦截器(Pipes,Interceptor)

管道是具有 @Injectable() 装饰器的类。管道应实现 PipeTransform 接口

Pipe_1

管道有两个类型:

  • 转换:管道将输入数据转换为所需的数据输出,
  • 验证:对输入数据进行验证,比如form表单提交的数据类型

拦截器是使用 @Injectable() 装饰器注解的类。拦截器应该实现 NestInterceptor 接口。

Interceptors_1

拦截器具有一系列有用的功能,这些功能受面向切面编程(AOP)技术的启发。它们可以:

  • 在函数执行之前/之后绑定额外逻辑
  • 转换从函数返回的结果
  • 转换从函数抛出的异常
  • 扩展基本函数行为
  • 根据所选条件完全重写函数 (例如, 缓存目的)
代码语言:javascript复制
/* logging.interceptor.ts */

import { Injectable, NestInterceptor, ExecutionContext, CallHandler } from '@nestjs/common';
import { Observable } from 'rxjs';
import { tap } from 'rxjs/operators';

@Injectable()
export class LoggingInterceptor implements NestInterceptor {
  intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
    console.log('Before...');

    const now = Date.now();
    return next
      .handle()
      .pipe(
        tap(() => console.log(`After... ${Date.now() - now}ms`)),
      );
  }
}

上面例子是一个统计请求时间的一个拦截器。那么基于拦截器功能我们能够实现统计时间过长的响应、统一响应体格式、捕获异常统一异常code码等功能。

四、模块

模块是具有 @Module() 装饰器的类。@Module() 装饰器提供了元数据,Nest 用它来组织应用程序结构。

Modules_1

从图片可以看出, Module的作用就是组织代码结构,CatsControllerCatsService属于同一个应用程序域,应该考虑将它们移动到一个功能模块下,即CatsModule

代码语言:javascript复制
/* cats/cats.module.ts */

import { Module } from '@nestjs/common';
import { CatsController } from './cats.controller';
import { CatsService } from './cats.service';

@Module({
  controllers: [CatsController],
  providers: [CatsService],
})
export class CatsModule {}
代码语言:javascript复制
/* app.module.ts */

import { Module } from '@nestjs/common';
import { CatsModule } from './cats/cats.module';

@Module({
  imports: [CatsModule],
})
export class ApplicationModule {}

目前的项目目录结构:

代码语言:javascript复制
src
├──cats
│    ├──dto
│    │   └──create-cat.dto.ts
│    ├──interfaces
│    │     └──cat.interface.ts
│    ├─cats.service.ts
│    ├─cats.controller.ts
│    └──cats.module.ts
├──app.module.ts
└──main.ts

虽然我们可以使用模块来组织代码,但是在微服务流行的情况下,模块的作用就不是很大了。

五、微服务

Microservices_1

Nest 支持几种内置的传输层实现,称为传输器,负责在不同的微服务实例之间传输消息。大多数传输器本机都支持请求 - 响应和基于事件的消息样式。默认情况下,微服务通过TCP协议监听消息。

代码语言:javascript复制
/* main.ts */

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,
    },
  );
  app.listen(() => console.log('Microservice is listening'));
}
bootstrap();
代码语言:javascript复制
/* cats.controller.ts */

import { Controller, Get, Post, Body } from '@nestjs/common';
import { MessagePattern } from '@nestjs/microservices';
import { CreateCatDto } from './dto/create-cat.dto';
import { CatsService } from './cats.service';
import { Cat } from './interfaces/cat.interface';

@Controller('cats')
export class CatsController {
  constructor(private catsService: CatsService) {}

  @Post()
  async create(@Body() createCatDto: CreateCatDto) {
    this.catsService.create(createCatDto);
  }

  @Get()
  async findAll(): Promise<Cat[]> {
    return this.catsService.findAll();
  }
 
  @MessagePattern({ cmd: 'findAllCats' })
  async externalFindAll(): Promise<Cat[]> {
    return this.catsService.findAll();
  }
}

为了交换消息或将事件发布到 Nest 微服务,我们使用 ClientProxy 类, 它可以通过几种方式创建实例。此类定义了几个方法,例如send()(用于请求-响应消息传递)和emit()(用于事件驱动消息传递),这些方法允许您与远程微服务通信。其他应用(客户端)和远程服务通信如下:

代码语言:javascript复制
@Module({
  imports: [
    ClientsModule.register([
      { name: 'CAT_SERVICE', transport: Transport.TCP },
    ]),
  ]
})
代码语言:javascript复制
/* client.controller.ts */

import { Inject, Controller, Get, Post, Body } from '@nestjs/common';
import { ClientProxy } from '@nestjs/microservices';

@Controller('client')
export class ClientController {
  constructor(@Inject('CAT_SERVICE') private client: ClientProxy) {}
  
  @get()
  findAllCats(): Promise<Cat[]> {
    return this.client.send<number>()
  }
}

除了默认的Tcp,我们还可以使用Redis、RabbitMQ、gRPC等流行的消息中间件来实现服务通信。

结束语

通过本文可以发现, Nestjs是一个有完整应用架构的框架,和Express、Koa等框架相比,提供了从基础控制器能力,安全(认证、鉴权),数据库集成到微服务。可以说几乎和java的Spring框架一样提供了企业级服务支撑。

0 人点赞