NestJS 帶你飛! 시리즈 번역 18# Lifecycle Hooks

2023. 6. 13. 22:02개발 문서 번역/NestJS

728x90
이 포스팅은 「NestJS 기초실무 가이드 : 강력하고 쉬운 Node.js 웹 프레임워크로 웹사이트 만들기」
(서명: NestJS 基礎必學實務指南:使用強大且易擴展的 Node.js 框架打造網頁應用程式)
책이 출간되었습니다.

 

LifeCycle Hook이란 무엇인가요?

소개하기 전에 먼저 생명주기(Lifecycle, 生命週期)에 대한 이해가 필요합니다.

간단하게 사람에 비유하면 출생부터 사망까지가 하나의 완전한 생명 주기 사이클이라고 할 수 있습니다.

그렇다면 생명주기 훅(Lifecycle Hook, 生命週期鉤子)이란 무엇일까요?

바로 생명주기 내  특정 시점에서 발생하는 하나의 사건 라고 할 수 있습니다.
(ex: 아이가 태어난 후에는 국적을 부여받거나, 5세가 되면 유치원에 입학하는 등..)

프로그래밍의 분야에서도 생명주기라는 개념이 있으며, 간단하게 시작에서 종료로 나눌 수 있습니다.

일부 프레임워크에서는 특정 시간에 다른 이벤트를 발생 시키기 위해 생명주기 훅을 설계하기도 합니다.

(ex: 애플리케이션을 시작할 때 API를 호출하거나 종료할 때 로그 정보를 남기는 등의 동작)

 

 

Nest Lifecycle Hooks

Nest에도 Lifecycle Hook이 있습니다.

순서에 따라 나누면 5개의 주기가 존재합니다.

 

1. Module 초기화 단계 (onModuleInit) 

2. Nest App 시작 단계 (onApplicationBootstrap)

3. Module 소멸 단계 (onModuleDestory)

4. Nest App 종료 전 단계 (beforeApplicationShutdown)

5. Nest App 종료 단계 (onApplicationShutdown)

 

Lifecycle Hooks이 '시작' 과 '종료' 시점에 따라 발생한다는 것과

modules, controllers, injectables에서 실행될 수 있다는것을 알 수 있습니다.

특히 종료 시점에서의 Hook은 반드시 bootstrap 실행 시 app.enableShutdownHooks()을 호출하여 기능을 활성화 해야합니다. 이 작업은 app.close()가 실행되거나 시스템 종료 신호(Ctrl + C)가 수신 되었을 때 발동됩니다.

 

주의: 종료시 실행되는 Hook은 이벤트 감지에 더 많은 자원을 소비하기 때문에 기본값은 비활성화되어 있습니다.

 

onModuleInit

onModuleInit은 해당 모듈 의존성 처리가 완료된 후에 호출됩니다.

예를 들어 Nest App에 AppModule, TodoModule이 있고, AppModuleTodoModule을 불러왔다고 가정해보겠습니다.

AppModule이 먼저 로드 되며 불러와질 때 해당 모듈의 의존성인
TodoModule, AppController, AppService도 같이 읽어들입니다.

즉 의존성들이 먼저 onModuleInit을 호출한 뒤에서야 AppModuleonModuleInit이 호출되어집니다.

아래는 이를 그림으로 표현한 예제입니다.

그림에서는 AppModule -> TodoModule이 불러와진 후 TodoController의 Hook, TodoService의 Hook,이 실행되며 TodoModule의 Hook이 실행되어 AppController의 Hook, AppService의 Hook이 실행됩니다. 마지막으로 AppModule의 Hook이 실행됩니다.

 

사용 방법은 매우 간단한데요, AppModule이 onModuleInit을 사용한다고 가정해봅시다.

OnModuleInit 인터페이스를 구현한 후 onModuleInit 메서드를 추가시키면 됩니다. 간단하죠?

import { Module, OnModuleInit } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';

@Module({
  imports: [
  ],
  controllers: [
    AppController
  ],
  providers: [
    AppService
  ]
})
export class AppModule implements OnModuleInit {
  onModuleInit(): void {
    console.log('[AppModule]: initial event!');
  }
}

 

이렇게 작성하고 나서 서버를 키면 초기화 단계에서 터미널에 바로 문자열이 출력됩니다.

[AppModule]: initial event!

 

onApplicationBootstrap

onApplicationBootstrap은 Nest App 모든 모듈이 초기화 된 후에 호출되며 연결이 성립되기 전에 발생됩니다.

onModuleInit의 실행 순서와 동일하며 의존성 모듈의 onApplicationBootstrap이 먼저 실행됩니다.

 

예를 들어 AppModuleonApplicationBootstrap을 사용하고자 한다면

OnApplicationBootstrap 인터페이스를 구현한 후 onApplicationBootstrap 메서드를 추가해주면 됩니다.

 

import { Module, OnApplicationBootstrap } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';

@Module({
  imports: [
  ],
  controllers: [
    AppController
  ],
  providers: [
    AppService
  ]
})
export class AppModule implements OnApplicationBootstrap {
  onApplicationBootstrap() {
    console.log('[AppModule]: bootstrap event!');
  }
}

 

이렇게 하면 서버 가동 후 터미널에 아래와 같은 문자열이 출력됩니다.

[AppModule]: bootstrap event!

 

onModuleDestory

onModuleDestory는 시스템의 종료 신호 혹은 app.close()가 호출되었을 때 실행됩니다.

onModuleInit의 실행 순서와는 조금 다른데요.

AppModule의 Controller와 Provider부터 onModuleDestory가 실행됩니다.
실행완료 후 AppModule 자체의 onModuleDestory가 발동됩니다.
그 다음에는 해당 모듈의 의존성 항목들의 Hook이 순차적으로 실행됩니다.

아래는 이를 표현한 순서도입니다.

 

 

주의: Nest 8 버전에서는 onModuleDestory의 실행 순서가 onModuleInit과 같습니다. 
역자 주 : 자세한 사항은 해당 커밋PR을 확인해주세요.

 

예를 들어 AppModule에서 onModuleDestory를 사용하고자 한다면

OnModuleDestory 인터페이스를 구현 한 후 onModuleDestory 메서드를 추가해주면 됩니다.

import { Module, OnModuleDestroy } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';

@Module({
  imports: [
    TodoModule,
    UserModule
  ],
  controllers: [
    AppController
  ],
  providers: [
    AppService
  ]
})
export class AppModule implements OnModuleDestroy {
  onModuleDestroy(): void {
    console.log('[AppModule]: destroy event!');
  }
}

이렇게 작성하고 나서 서버를 종료하려고 할 때 터미널에 해당 문자열이 출력될것입니다.

[AppModule]: destroy event!

 

beforeApplicationShutdown

beforeApplicationShutdown은 Nest App이 모든 연결을 닫을 때 호출되어 app.close()를 실행시킵니다.

onModuleInit의 실행 순서와 동일하며 의존성 모듈의 beforeApplicationShutdown이 먼저 실행됩니다.

예를 들어 AppModule에서 beforeApplicationShutdown을 사용하고자 한다면

BeforeApplicationShutdown 인터페이스를 구현한 후 beforeApplicationShutdown 메서드를 추가시켜주면 됩니다.

import { BeforeApplicationShutdown, Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';

@Module({
  imports: [
  ],
  controllers: [
    AppController
  ],
  providers: [
    AppService
  ]
})
export class AppModule implements BeforeApplicationShutdown {
  beforeApplicationShutdown(): void {
    console.log('[AppModule]: before shutdown event!');
  }
}

 

이렇게 하면 연결을 종료하기 전에 아래의 문자열이 터미널에 출력될것입니다.

[AppModule]: before shutdown event!

 

onApplicationShutdown

onApplicationShutdown은 Nest App이 모든 연결을 닫을 때 호출됩니다.

onModuleInit의 실행 순서와 동일하며 의존성 모듈의 onApplicationShutdown이 먼저 실행됩니다.

예를 들어 AppModule에서 onApplicationShutDown을 사용하려면

OnApplicationShutdown 인터페이스를 구현한 후 onApplicationShutdown 메서드를 추가하면 됩니다.

import { Module, OnApplicationShutdown } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';

@Module({
  imports: [
  ],
  controllers: [
    AppController
  ],
  providers: [
    AppService
  ]
})
export class AppModule implements OnApplicationShutdown {
  onApplicationShutdown(): void {
    console.log('[AppModule]: shutdown event!');
  }
}

 

이렇게 작성하고 나면 모든 연결이 닫힐 때 터미널에 해당 문자열이 출력됩니다.

[AppModule]: shutdown event!

 

마치며

Lifecycle Hooks를 사용하면 적절한 시점에 적절한 작업을 수행할 수 있습니다.

종료 시에 호출되는 Hook은 주로 Kubernetes 와 같은 컨테이너 오케스트레이션 서비스에서 사용됩니다.

아래는 오늘 포스팅의 요약본입니다.

 

1. Nest의 Lifecycle Hooks는 5종류가 있으며 '시작'과 '종료' 이 두개의 시점에서 발동됩니다.

2. 5개의 Hook은 onModuleInit, onApplicationBootstrap, onModuleDestory, beforeApplicationShutdownonApplicationShutdown으로 구분됩니다.

3. '종료'시 실행되는 Hook은 app.enableShutdownHooks()을 통해 해당 Hook을 활성화시켜야 합니다.