Skip to content

AOP 架构

后端架构基本上 MVC 架构。MVC 是 Model View Control 的缩写,在 MVC 架构下,请求发送给 Controller,由它调用 Model 层的 Service 来完成业务逻辑,然后返回对应的 View。

在这个流程中,Nest 还提供了 AOP 的能力。AOP 是 Aspect Oriented Programming 的缩写,指面向切面编程。

面向切面编程

一个请求进来,可能会经过 Controller、Service、Repository。

如果我们想在这个调用链路中加入一些通用的逻辑该怎么办呢?比如日志记录、权限控制、异常处理等。

容易想到的方法是直接修改 Controller 的代码,加入新的逻辑。问题虽然解决了,但是这些通用的逻辑侵入了业务层,也不能做到很方便的复用。怎么样才能透明、无感地加上这些逻辑呢?AOP 解决了这个问题。

AOP 在 Controller 之前创建了一个切面,把一些通用逻辑分离在切面中,保持业务逻辑的纯粹性。切面逻辑可以复用,还可以动态地增删。

Express 的洋葱模型其实也是一种 AOP 的实现,在外面包一层内层感知不到的逻辑。

Nest 实现 AOP 的方式一共有五种:Middleware、Guard、Pipe、Interceptor 和 ExceptionFilter。

Middleware

Nest 的底层是 Express,也继承了 Middleware。Middleware 即中间件,分为两种,全局 Middleware 和路由 Middleware。

全局 Middleware 在请求之前或之后加入一些处理逻辑:

ts
async function bootstrap() (
  const app = await NestFactory.create(AppModule);
  app.use(Logger);
  await app.listen(3000);
)

路由 Middleware 是只作用于某个路由:

ts
export class AaaModule implements NestModule (
  configure(consumer: MiddlewareConsumer) (
    consumer.apply(Logger).forRoutes('cats')
  )
 )

Guard

Guard 即路由守卫,用于在调用某个 Controller 之前判断权限,决定是否放行。

Guard 实现 CanActive 接口,调用 canActive 方法,从 context 拿到请求信息,做一些权限验证等处理后返回布尔值。

ts
@Injectable()
export class Guard implements CanActivate {
  canActivate(
    context: ExecutionContext,
  ): boolean | Promise<boolean> | Observable<boolean> {
    return true
  }
}

通过装饰器注入 Controller。

ts
@UseGuards(Guard)
@Controller('aaa')

同样地,Guard 也支持全局注入。

ts
async function bootstrap() {
  const app = await NestFactory.create(AppModule)
  app.useGlobalGuards(Guard)
  await app.listen(3000)
}

Interceptor

Interceptor 指拦截器,可以在目标 Controller 方法前后的 Request 或 Response 加入逻辑。

Interceptor 实现 NestInterceptor 接口,调用 intercept 方法。调用 next.handle() 即在 Controller 之前或之后加入逻辑。

ts
@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`)))
  }
}

通过装饰器注入 Interceptor。

ts
@UseInterceptors(LoggingInterceptor)
@Controller('aaa')

Interceptor 也支持全局注入。

ts
async function bootstrap() {
  const app = await NestFactory.create(AppModule)
  app.useGlobalInterceptors(new LoggingInterceptor())
  await app.listen(3000)
}

Pipe

Pipe 是管道的意思,用来对参数做校验或转换。

Pipe 实现 PipeTransform 接口,调用 transform 方法,传入 value 值做验证,比如格式、类型是否正确。若不正确则抛出异常。也可以做转换,返回转换后的值。

ts
@Injectable()
export class ValidationPipe implements PipeTransform {
  transform(value: any, metadata: ArgumentMetadata) {
    return value
  }
}

Pipe 有九种类型,从名字就能知道是什么作用:

  • ValidationPipe
  • ParseIntPipe
  • ParseBoolPipe
  • ParseArrayPipe
  • ParseUUIDPipe
  • DefaultValuePipe
  • ParseEnumPipe
  • ParseFloatPipe
  • ParseFilePipe

在特定路由下注入 Pipe,将 Pipe 与上下文进行绑定。

ts
  @Get(':id')
  findOne(@Param('id', ParseIntPipe) id: string) {
    return this.aaaService.findOne(+id);
  }

在全局使用 Pipe。

ts
async function bootstrap() {
  const app = await NestFactory.create(AppModule)
  app.useGlobalPipes(new ValidationPipe())
  await app.listen(3000)
}
bootstrap()

ExceptionFilter

无论是 Guard、Interceptor、Pipe 或 Controller,过程中都可以抛出异常。这些异常也是一种通用逻辑,Nest 通过 ExceptionFilter 来对抛出的异常进行处理。

首先实现 ExceptionFilter 接口,调用 catch 方法就可以拦截异常。拦截什么类型的异常通过装饰器 Catch 来指定。拦截异常后,可以返回对应的响应,给用户更好的提示。

ts
@Catch(HttpException)
export class HttpExceptionFilter implements ExceptionFilter {
  catch(exception: HttpException, host: ArgumentsHost) {
    const ctx = host.switchToHttp()
    const response = ctx.getResponse<Response>()
    const request = ctx.getRequest<Request>()
    const status = exception.getStatus()

    response.status(status).json({
      statusCode: status,
      timestamp: new Date().toISOString(),
      path: request.url,
    })
  }
}

Nest 也内置了多种 HTTP 相关的异常,都是 HttpException 的子类:

  • BadRequestException
  • UnauthorizedException
  • NotFoundException
  • ForbiddenException
  • NotAcceptableException
  • RequestTimeoutException
  • ConflictException
  • GoneException
  • PayloadTooLargeException
  • UnsupportedMediaTypeException
  • UnprocessableException
  • InternalServerErrorException
  • NotImplementedException
  • BadGatewayException
  • ServiceUnavailableException
  • GatewayTimeoutException

也可以根据自己的需求扩展。

ts
@Catch()
export class AllExceptionsFilter extends BaseExceptionFilter {
  catch(exception: unknown, host: ArgumentsHost) {
    super.catch(exception, host)
  }
}

ExceptionFilter 针对某个路由生效。

ts
@Post()
@UseFilters(new HttpExceptionFilter())
async create(@Body() createCatDto: CreateCatDto) {
  throw new ForbiddenException();
}

抑或是全局生效。

ts
async function bootstrap() {
  const app = await NestFactory.create(AppModule)
  app.useGlobalFilters(new HttpExceptionFilter())
  await app.listen(3000)
}

AOP 机制的调用顺序

Nest 接收到 Request 后,Middleware 在最外层被调用,依次经过 Guard、Pipe 和 ExceptionFilter,最后返回 Response。Interceptor 在 Pipe 的前后可供调用。

Released under the MIT License.