NestJS – 라이프 사이클(생명주기 이벤트)

NestJS를 활용하여 프로젝트를 진행하면 NestJS의 라이프 사이클에 대한 궁금증이 생기게 됩니다.

NestJS안에는 다양한 모듈과 기능이 있기 때문에 어떤 순서로 돌아가는지, 어떤 상황에서 사용해야 하는지 잘 알아야 활용 가능합니다.

이번 글에서는 NestJS가 동작하는 LifeCycle은 어떤지 확인하고, 언제 활용해야 하는지 알 수 있도록 살펴보겠습니다.

라이프 사이클 이벤트(Lifecycle Event)

생명주기는 무엇일까요? 생명주기는 하나의 프레임워크가 시작부터 끝까지 어떻게 동작하는지 정의된 것이라고 생각하면 쉽습니다. 시작 메서드는 무엇이고, 종료될 때는 어떤 메서드로 끝나는지를 나타냅니다. 메서드가 아닌 문법으로 끝날 수도 있죠.

여기서 생명주기 이벤트라는 것이 있는데요. Nest는 두 가지 기능을 제공합니다.

  • 주요 생명 주기 이벤트에 대한 가시성을 제공하는 수명 주기 훅
  • 이벤트가 발생할 때 조치를 취할 수 있는 기능

생명 주기 순서

아래 다이어그램은 공식 문서에 있는 애플리케이션 생명 주기 이벤트의 순서입니다.

위 다이어그램을 보면 전체 라이프사이클이 초기화, 실행, 종료 세 단계로 나눌 수 있습니다.

라이프사이클을 알아야 할 가장 큰 이유는 모듈과 서비스의 적절한 초기화 계획을 세우고, 활성 연결을 관리하며, 종료 신호를 받아 애플리케이션을 정상적으로 종료시킬 수 있기 때문입니다.

NestJS 실행 로그
NestJS - 라이프 사이클(생명주기 이벤트) 5

위와 같이 로그를 보면 초기화 후 실행 단계를 확인할 수 있습니다.

  • Module initialized
  • BootStrap
  • Mapped route

생명주기 이벤트

앞서 생명주기(LifeCycle)를 이야기했고, 그다음 생명주기 이벤트를 살펴보겠습니다.

NestJS 라이프사이클
NestJS - 라이프 사이클(생명주기 이벤트) 6

생명주기 이벤트는 애플리케이션 부트스트래핑(bootstraping) 및 종료 중에 발생합니다. Nest는 각 생명 주기 이벤트에서 모듈, 공급자 및 컨트롤러에 등록된 훅 메서드를 호출하죠.

아래 표는 앞서 말한 생명 주기 훅에 대한 메서드를 정리한 표입니다.

생명 주기 훅 Methode후크 Method에 대한 설명
onModuleInit()호스트 모듈의 종속성이 해결되면 호출되는 메서드
onApplicationBootstrap()모든 모듈이 초기화 된 후 호출되는 메서드. 연결 수신 전에 호출
onModuleDestroy()종료 신호가 수신된 후 호출
beforeApplicationShutdown()모든 onModuleDestory() 핸들러가 완료된 후 호출
완료되면 기존의 모든 연결이 닫힘.
onApplicationShutdown()연결이 닫힌 후 호출됨.

생명주기 이벤트는 어떻게 사용할까요?

여기에는 문제 정의 및 상황 설정이 필요합니다. 먼저, 특정 서비스를 Module를 초기화할 때 호출하여 Log를 출력하고 싶다는 가정으로 시작해 보죠. 생명주기 이벤트는 이와 같은 상황에서 특정 호출로 시작하는 것이 아닌 애플리케이션이 실행될 때 필요한 것을 호출할 수 있습니다.

import { Injectable, OnModuleInit } from '@nestjs/common';

@Injectable()
export class UsersService implements OnModuleInit {
  onModuleInit() {
    console.log(`The module has been initialized.`);
  }
}

위 코드는 OnModuleInit를 시작할 때 console.log를 실행하도록 만든 코드입니다.

좀 더 예시를 든다면 아래와 같은 코드도 가능합니다.

// import

@Injectable()
export class PushNotificationService implements OnApplicationBootstrap {
  constructor(
    private readonly schedulerRegistry: SchedulerRegistry,
    private readonly configService: ConfigService,
    @InjectRepository(User)
    private readonly userRepository: Repository<User>,
  ) {}

  onApplicationBootstrap() {
    // 메일 삭제 푸시 알림 작동
    this.sendDeleteMailPushNotification();

    // 환경 보호 정보 알림 작동
    this.sendInformationPushNotification();
  }

  async sendDeleteMailPushNotification() {
    const auth = this.configService.get('googleAuth');
    const oAuth2Client = this.configService.get('googleOAuth2Client');

    const pubSubClient = new PubSub({auth});
    const subscription = pubSubClient.subscription(
      this.configService.get('googlePubSub').subscriptionName,
    );
 ...

위 코드는 NestJS가 BootStrap 후에, 다른 API 호출 없이 구글로부터 response를 받는 코드입니다.

애플리케이션이 실행되면 바로 호출해 가져오는 걸 확인할 수 있습니다.

좀 더 다양한 사례를 살펴보겠습니다.

비동기 초기화 같은 경우 OnModuleInit와 OnApplicationBootstrap를 사용해서 애플리케이션 초기화 프로세스를 연기할 수 있습니다.

async onModuleInit(): Promise<void> {
  await this.fetch();
}

onModuleDestroy(), beforeApplicationShutdown()은 종료 단계에서 호출됩니다.

onApplicationShutdown 같은 경우 컨테이너의 수명 주기를 관리하는 kubernetes와 같은 서비스에서 사용됩니다.

종료 훅을 사용하기 위해서는 enableShutdownHooks()를 호출하여 리스너를 활성화해야 합니다.

import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);

  // Starts listening for shutdown hooks
  app.enableShutdownHooks();

  await app.listen(3000);
}
bootstrap();

애플리케이션 종료 Hook과 관련해서 Windows는 제한적으로 기능을 지원합니다.

그렇기 때문에 Windows에서는 NodeJs에 있는 문서를 참고하여, 제어해야 합니다. NodeJS에는 다른 방식으로 종료 Hook를 호출할 수 있습니다.

종료 신호를 받게 되면, onModuleDestroy(), beforeApplicationShutdown(), onApplicationShutdown() 메서드를 호출하게 됩니다. 이때 첫 번째 매개 변수로 신호를 받게 됩니다.

@Injectable()
class UsersService implements OnApplicationShutdown {
  onApplicationShutdown(signal: string) {
    console.log(signal); // e.g. "SIGINT"
  }
}

NestJS Reqeust & Response LifeCycle

Untitled
NestJS - 라이프 사이클(생명주기 이벤트) 7

NestJS의 생명주기를 살펴봤다면 이번에는 요청에 따른 생명 주기를 살펴보겠습니다.

애플리케이션에 요청이 오면 다양한 모듈을 거쳐 애플리케이션 내부로 들어옵니다.

  1. Middleware
    1. Global middleware
    2. Module middleware
  2. Guards
    1. Global guard
    2. Controller guard
    3. Route guard
  3. Pre Interceptors
    1. Global interceptiors
    2. Controller interceptors
    3. Route interceptors
  4. Pipes
    1. Global pipes
    2. Controller pipes
    3. Route pipes
    4. Route parameter pipes
  5. Controller – method handler
  6. Service – 없을 수도 있음.
  7. Post Interceptors
    1. Route Interceptor
    2. Controller Interceptor
    3. Global Interceptor
  8. Filters
    1. Route Exception Filters
    2. Controller Exception filters
    3. Global Exception filters

위와 같은 순서로 다양한 모듈을 통과하여, Response를 반환합니다.

위 순서를 알아야 하는 이유는 각 순서에 맞게 우리가 필요한 기능을 넣어 미리 실행하거나 나중에 실행시킬 필요가 있기 때문입니다.

위와 같은 순서로 실행되는 것을 알지 못하면, 잘못된 위치에 기능을 추가하고, 원하지 않는 시기에 기능이 실행되는 것을 볼 수 있습니다.

각각의 자세한 설명 및 내용은 다음 시간에 다뤄보도록 하겠습니다.

참고자료

https://docs.nestjs.com/fundamentals/lifecycle-events#lifecycle-events-1

https://www.rldnd.net/nestjs-lifecycle-

https://jakekwak.gitbook.io/nestjs/fundamentals/lifecycle-events#onapplicationshutdown

https://velog.io/@haron/NestJS-Lifecycle-Events

Leave a Comment