Nestjs - Interceptor

atesi·2023년 4월 10일
0

이번 포스팅은 지난 포스팅에서 작성한 repository apiquerybuilder의 성능을 비교하기 위해 Interceptor를 사용하며 알게 된 내용을 정리해 보았습니다.

Request Lifecycle

NestJS에서는 미들웨어, 파이프, 가드, 인터셉터 등 다양한 기능들을 보유하고 있습니다. 우선 NestJSRequest Lifecycle부터 알아 보겠습니다.

  1. Incoming request
  2. Globally bound middleware, Module bound middleware
  3. Global guards, Controller guards, Route guards
  4. Global interceptors (pre-controller), Controller interceptors (pre-controller), Route interceptors (pre-controller)
  5. Global pipes, Controller pipes, Route pipes, Route parameter pipes
  6. Controller (method handler)
  7. Service (if exists)
  8. Route interceptor (post-request), Controller interceptor (post-request), Global interceptor (post-request)
  9. Exception filters (route, then controller, then global)
  10. Server response

Middleware vs Interceptor

Middleware

미들웨어는 라우트 핸들러 이전에 호출되는 함수입니다. 미들웨어 기능은 애플리케이션의 요청-응답 주기에서 요청 및 응답 객체와 next() 미들웨어 함수에 액세스할 수 있습니다.

Interceptor

인터셉터는 핸들러가 호출되기 전후에 경로를 가로챕니다. 즉, 요청 및 응답을 조작하여 추가 논리를 바인딩하고 결과를 변환하는 등의 작업을 수행할 수 있습니다.

가장 큰 차이점은 받는 파리미터가 다르다는 점 입니다.

Middleware는 파라미터로 request, response, next 받아HTTP가 아니면 사용이 불가합니다.

반면에 Interceptor는 파라미터로 ExecutionContextCallHandler를 받아 처리 합니다.

import { Type } from '../index';
import { ArgumentsHost } from './arguments-host.interface';
/**
 * Interface describing details about the current request pipeline.
 *
 * @see [Execution Context](https://docs.nestjs.com/guards#execution-context)
 *
 * @publicApi
 */
export interface ExecutionContext extends ArgumentsHost {
    /**
     * Returns the *type* of the controller class which the current handler belongs to.
     */
    getClass<T = any>(): Type<T>;
    /**
     * Returns a reference to the handler (method) that will be invoked next in the
     * request pipeline.
     */
    getHandler(): Function;
}

첫번째 파라미터인 ExecutionContextArgumentsHost를 확장하여 현재 실행 프로세스에 대한 추가 세부 정보를 제공합니다.

ArgumentsHost는 단순히 핸들러의 인수에 대한 추상화 역할을 합니다. 예를 들어 HTTP 서버 애플리케이션의 경우 호스트 객체는 Express[request, response, next] 배열을 캡슐화합니다. 반면에 GraphQL의 경우 호스트 객체에는 [root, args, context, info] 배열을 포함합니다.

때문에 InterceptorHTTP 이외에도 WebSocket, GraphQL, RPC(Remote procedure call)에서도 동작 가능합니다.

Observable?

두번째는 Observable 객체인 CallHandler입니다.
처리과정을 살펴보면 /catsPOST요청이 들어오면. 이 요청은 CatsController 내부에 정의된 create() 핸들러로 향합니다. handle() 메서드를 호출하지 않는 인터셉터가 중간에 호출되면 create() 메서드는 실행되지 않습니다. handle()이 호출되고 Observable이 반환되면 create() 핸들러가 트리거됩니다. 그리고 응답 스트림이 Observable을 통해 수신되면 스트림에서 추가 작업을 수행할 수 있으며 최종 결과는 호출자에게 반환됩니다.

여기서 Observable를 좀더 알아보겠습니다.

RxJSobservable sequences를 사용하여 비동기 및 이벤트 기반 프로그램을 구성하기 위한 라이브러리입니다. 하나의 핵심 유형, Observable, 위성 유형(Observer, Schedulers, Subjects) 및 Array 메서드(map, filter, reduce, every 등)에서 영감을 받은 연산자를 제공하여 비동기 이벤트를 컬렉션으로 처리할 수 있습니다.

RxJS에서 컬렉션은 단일 엔터티로 조작 및 변환할 수 있는 관찰 가능한 스트림 그룹을 나타냅니다. 컬렉션은 여러 Observable을 결합하여 생성할 수 있으며 RxJS에서 제공하는 다양한 연산자를 사용하여 조작할 수 있습니다.

스트림은 시간의 흐름에 따라 발생하는 일련의 이벤트입니다.

핵심 유형인 ObservableObserver를 인수로 취한 다음 시간 경과에 따라 일련의 값을 생성하는 함수로 생각할 수 있습니다.

ObserverObservable의 동작을 정의하는 객체로 next, error, complete를 사용해 event을 처리 합니다.

Setup

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

@Injectable()
export class QuerySpeedInterceptor implements NestInterceptor {
  intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
    const start = Date.now();
    return next.handle().pipe(
      tap(async () => {
        const end = Date.now();
        const time = end - start;
        const logger = new Logger();
        logger.debug(`Query executed in ${time}ms.`, `Database`);
      }),
    );
  }
}

Interceptor 내에서 쿼리 시작 시간은 Date.now() 메서드를 사용하여 기록됩니다. 그런 다음 next.handle() 메서드로 Observable객체로 만들어 줍니다. 다음pipe 메서드가 호출되어 Observable에 연산자를 적용할 수 있습니다.
RxJS에서 tapemitted valueObservable stream을 수정하지 않고 Observable이 내보낸 값에 대해 Side Effect없이 수행할 수 있게 해주는 연산자입니다.
위의 경우 tap 연산자는 쿼리 실행 시간을 기록하는 함수를 실행하는 데 사용됩니다. tap 함수 내에서 Date.now()를 사용하여 쿼리의 종료 시간을 기록하고 실행 시간을 얻기 위해 시작 시간과 종료 시간의 차이를 계산합니다. 마지막으로 debug 메서드를 사용하여 실행 시간을 기록하기 위해 Logger 인스턴스를 생성합니다.

@Get('/find/:text')
  @UseFilters(QueryFailedExceptionFilter)
  @UseInterceptors(QuerySpeedInterceptor)
  async findRoomByText(@Param('text') text:string): Promise<RoomEntity[]> {
    return await this.roomsService.findRoomByText(text);
  }

컨트롤러에 적용해 줍시다.

요청이 수행될 때 마다 로그가 발생하는 모습.




참고
docs.nestjs.com/interceptors
RxJS
NestJS Interceptor와 Lifecycle
[NestJS] Lifecycle Events

profile
Action!

0개의 댓글