iT邦幫忙

2024 iThome 鐵人賽

DAY 10
1

nestjs 分層架構 Interceptor

目標

  1. 介紹 nestjs Interceptor
  2. 介紹 AOP 概念

概念

nestjs Interceptor 提供一個元件可以攔截 Controller 內部 handler 處理的前後邏輯,並且可以對輸入輸出結果做處理。

nestjs Interceptor 元件主要是受到 AOP (也就是關注點導向程式設計)的啟發。希望透過 Interceptor 把一些非功能性的處理邏輯(比如錯誤處理或是驗證授權)獨立出來。能夠在不影響功能性的處理邏輯下,增強原本的邏輯。

AOP(Aspect Oriented Programming) 概念

在原本的物件導向世界,是透過物件把職責切分,物件間的互動來建構整個系統功能。然後一些在功能性的一些邏輯,比如在上傳檔案功能中的驗證授權或是稽核紀錄,這類邏輯往往無法在一開始就做設計,變成需要不斷去修改架構。為了這些功能可以更符合開閉原則,在其之上衍生出關注點導向的設計(也就是AOP),把這類邏輯稱為關注點,透過代理物件把這類邏輯隔離出來。

關注點導向的設計(也就是AOP)特色,在於基於原本的邏輯,把一些非功能性的職責獨立在關注點的部份。比如對原本邏輯上的例外處理或是權限驗證。這個部份,在 nestjs 框架提供了 Interceptor 元件來做這樣的邏輯管理。

image

語法

要實作 Interceptor,需要實作以下條件:

  1. 實作 NestInterceptor 介面
  2. 複寫 intercept 方法
  3. 為了注入一些關注點預設方法,所以需要加上 @Injectable 裝飾子
import {
  CallHandler,
  ExecutionContext,
  HttpException,
  Injectable,
  Logger,
  NestInterceptor,
} from '@nestjs/common';
import { Response } from 'express';
import { Observable, catchError, tap, throwError } from 'rxjs';
import { RequestService } from 'src/request.service';

@Injectable()
export class LoggingInterceptor implements NestInterceptor {
  private readonly logger = new Logger(LoggingInterceptor.name);
  constructor(private readonly requestService: RequestService) {}
  intercept(
    context: ExecutionContext,
    next: CallHandler<unknown>,
  ): Observable<unknown> | Promise<Observable<unknown>> {
    const request = context.switchToHttp().getRequest();
    const userAgent = request.get('user-agent') ?? '';
    const { ip, method, path: url } = request;
    this.logger.log(
      `${method} ${url} ${userAgent} ${ip}: ${context.getClass().name} ${
        context.getHandler().name
      } invoked...`,
    );
    this.logger.debug('userId:', this.requestService.getUserId());
    const now = Date.now();
    return next.handle().pipe(
      tap((res) => {
        const response = context.switchToHttp().getResponse();
        const { statusCode } = response;
        const contentLength = response.get('content-length');
        this.logger.log(
          `${method} ${url} ${statusCode} ${contentLength} - ${userAgent} ${ip}: ${
            Date.now() - now
          } ms`,
        );
        this.logger.debug('Response:', res);
      }),
      catchError((error) => {
        const response: Response = context.switchToHttp().getResponse();
        // const { statusCode } = response;
        const httpError = error as HttpException;
        const statusCode =
          httpError.getStatus() ?? response.statusCode ?? undefined;
        this.logger.error(
          `${method} ${url} ${statusCode} - ${userAgent} ${ip}: ${
            context.getClass().name
          } ${context.getHandler().name} ${Date.now() - now}ms`,
        );
        return throwError(() => error);
      }),
    );
  }
}

作用範圍

作用範圍一共有三種,可以需依據去自行套用

1. Method 範圍

只想針對某個 method 做攔截,直接套用在某個 Controller 的 route handler 上,如下:

@Post()
@UseInterceptors(LoggingInterceptor)
createPet(@Body() createPetDto: CreatePetDto) {
  return this.petsService.createPet(createPetDto);
}

2. Controller 範圍

針對某個 Prefix 的 route 做攔截,直接套用在某個 Controller 的 class 上,如下:

@UseInterceptors(LoggingInterceptor)
export class PetsController {}

3. app 範圍

要針對所有 route 做攔截,有兩種作法:

  1. 不透過注入,直接寫入 main.ts
const app = await NestFactory.create(AppModule);
app.useGlobalInterceptors(new LoggingInterceptor());
  1. 在 app.module.ts 透過 Provide 方式住入
import { Module } from '@nestjs/common';
import { APP_INTERCEPTOR } from '@nestjs/core';

@Module({
  providers: [
    { provide: APP_INTERCEPTOR, useClass: LoggingInterceptor }
  ],
})
export class AppModule {}

結論

nestjs Interceptor 提供了可以優雅地擴充原本 route 處理邏輯的方法。一般來說會把非功能性邏輯,比如例外處理,設置 timeout 等等邏輯放在上面。要注意的事,不要把一些職責的任務切分過細,有時反而會讓邏輯細碎。


上一篇
nestjs 分層元件 Filter
下一篇
nestjs 中斷點設置 with vscode
系列文
透過 nestjs 框架,讓 nodejs 系統維護度增加31
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言