NestJS 공식문서 Lifecycle Events

GGAE99·2023년 7월 25일
0

NestJS 공식 문서

목록 보기
12/33
post-thumbnail

Lifecycle Events

Nest 애플리케이션 및 모든 애플리케이션 요소는 Nest에 의해 관리되는 라이프사이클을 가지고 있습니다.
Nest는 라이프사이클 훅을 제공하여 주요 라이프사이클 이벤트에 대한 가시성을 제공하고 발생할 때 등록된 코드(모듈, injectable 또는 컨트롤러에서 등록된 코드)를 실행할 수 있도록 합니다.

Lifecycle sequence

이 라이프사이클은 애플리케이션이 부팅되는 시점부터 노드 프로세스가 종료될 때까지의 주요 이벤트 순서를 보여줍니다. 전반적인 라이프사이클은 초기화(initializing), 실행(running), 종료(terminating)로 세 가지 단계로 나눌 수 있습니다. 이 라이프사이클을 활용하여 모듈과 서비스의 적절한 초기화를 계획하고 활성 연결을 관리하며, 애플리케이션이 종료 신호를 받을 때 우아하게 종료할 수 있습니다.

nest 라이프사이클

아래는 글 내용 번역입니다.

Nest 핵심 부트스트래핑

각 모듈마다, 모듈 초기화 이후에 다음과 같은 단계가 수행됩니다:
자식 컨트롤러와 프로바이더의 onModuleInit() 메서드를 기다립니다.
모듈 자체의 onModuleInit() 메서드를 기다립니다.

각 모듈마다 다음과 같은 단계가 수행됩니다:
자식 컨트롤러와 프로바이더의 onApplicationBootstrap() 메서드를 기다립니다.
모듈 자체의 onApplicationBootstrap() 메서드를 기다립니다.

HTTP 서버, WebSocket 서버, 각각의 마이크로서비스에 경우:
연결이 열리거나 준비될 때까지 대기합니다.
일반적인 애플리케이션 처리

종료 신호가 수신되었습니다.

각 모듈마다 다음과 같은 단계가 수행됩니다:
자식 컨트롤러와 프로바이더의 onModuleDestroy() 메서드를 기다립니다.
모듈 자체의 onModuleDestroy() 메서드를 기다립니다.

각 모듈마다 다음과 같은 단계가 수행됩니다:
자식 컨트롤러와 프로바이더의 beforeApplicationShutdown() 메서드를 기다립니다.
모듈 자체의 beforeApplicationShutdown() 메서드를 기다립니다.

HTTP 서버, WebSocket 서버, 각각의 마이크로서비스에 경우:
연결이 종료될 때까지 대기합니다.
각 모듈마다 다음과 같은 단계가 수행됩니다:

자식 컨트롤러와 프로바이더의 onApplicationShutdown() 메서드를 기다립니다.
모듈 자체의 onApplicationShutdown() 메서드를 기다립니다.

프로세스가 종료됩니다.

이와 같이 Nest는 각 모듈이나 컨트롤러, 프로바이더 등 초기화, 실행, 종료 등의 단계에서 특정한 메서드들을 실행할 수 있는 라이프사이클 훅을 제공합니다. 이를 통해 애플리케이션의 초기화 및 실행 중에 필요한 작업들을 수행하고, 종료 시에는 안정적인 처리를 할 수 있습니다. Nest는 이러한 라이프사이클 관리를 통해 애플리케이션의 동작을 효율적으로 관리하고 유연성을 제공합니다.

Lifecycle events

라이프사이클 이벤트는 애플리케이션 부트스트래핑과 종료 시에 발생합니다. Nest는 등록된 라이프사이클 훅 메서드들을 모듈, injectable, 컨트롤러 등에서 각 라이프사이클 이벤트마다 호출합니다. (종료 훅은 먼저 활성화해야 하며, 아래의 "Application shutdown"을 참조하세요). 위의 다이어그램에서도 보여지듯이, Nest는 또한 연결 수신을 시작하고 연결 수신을 중단하기 위해 적절한 내부 메서드를 호출합니다.

다음 표에서, onModuleDestroy, beforeApplicationShutdownonApplicationShutdownapp.close()를 명시적으로 호출하거나 프로세스가 특별한 시스템 신호 (예: SIGTERM)를 수신하고 애플리케이션 부트스트래핑 시에 올바르게 enableShutdownHooks를 호출한 경우에만 트리거됩니다 (아래의 "Application shutdown" 참조).

Lifecycle hook methodLifecycle event triggering the hook method call
onModuleInit()호스트 모듈의 종속성이 해결된 후에 한 번 호출됩니다.
onApplicationBootstrap()모든 모듈이 초기화된 후에 호출되지만 연결 수신을 시작하기 전에 호출됩니다.
onModuleDestroy()*종료 신호 (예: SIGTERM)를 수신한 후에 호출됩니다.
beforeApplicationShutdown()*모든 onModuleDestroy() 핸들러가 완료된 후 (Promises가 해결되거나 거부된 후) 호출됩니다. 이후 모든 기존 연결이 닫힐 것이며 (app.close()가 호출될 것입니다).
onApplicationShutdown()*연결이 닫힌 후 호출됩니다 (app.close()가 해결될 때 호출됩니다).

이러한 이벤트들에 대해서는, 명시적으로 app.close()를 호출하지 않는 경우, SIGTERM과 같은 시스템 신호와 함께 동작하도록 opt-in을 해야 합니다. 아래의 "Application shutdown"을 참조하세요.

Warning!!
위에서 나열한 라이프사이클 훅은 요청 범위(request-scoped) 클래스에 대해서는 트리거되지 않습니다. 요청 범위 클래스는 애플리케이션의 라이프사이클과 연결되지 않으며 수명은 예측할 수 없습니다. 이들은 각 요청마다 독립적으로 생성되고 응답이 전송된 후에 자동으로 가비지 컬렉션됩니다.

Hint!
onModuleInit()onApplicationBootstrap()의 실행 순서는 모듈을 임포트하는 순서에 직접적으로 의존합니다. 이전 훅을 기다리는 것에 따라 결정됩니다.

Usage

각 라이프사이클 훅은 인터페이스에 의해 나타납니다. 인터페이스는 기술적으로 선택적이며 TypeScript 컴파일 이후에는 존재하지 않습니다. 그러나 강력한 타이핑과 편집기 도구의 장점을 얻기 위해 인터페이스를 사용하는 것이 좋은 관행입니다. 라이프사이클 훅을 등록하려면 해당 인터페이스를 구현하면 됩니다. 예를 들어, 특정 클래스 (예: Controller, Provider 또는 Module)에서 모듈 초기화 중에 호출할 메서드를 등록하려면 아래와 같이 OnModuleInit 인터페이스를 구현하여 onModuleInit() 메서드를 제공하면 됩니다:

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

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

Asynchronous initialization

OnModuleInitOnApplicationBootstrap 훅은 애플리케이션 초기화 과정을 지연시킬 수 있도록 합니다. 이를 위해 메서드 본문에서 Promise를 반환하거나 메서드에서 asyncawait을 사용해 메서드 본문에서 비동기 메서드 완료를 기다릴 수 있습니다.

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

Application shutdown

onModuleDestroy(), beforeApplicationShutdown()onApplicationShutdown() 훅은 종료 단계에서 호출됩니다 (app.close()를 명시적으로 호출하거나 시스템 신호 (예: SIGTERM)를 받았을 때, 옵트인한 경우). 이 기능은 종종 Kubernetes를 사용하여 컨테이너의 라이프사이클을 관리하거나 Heroku의 dynos 또는 유사한 서비스에서 사용됩니다.

종료 훅 리스너는 시스템 리소스를 사용하므로 기본적으로 비활성화됩니다. 종료 훅을 사용하려면 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();

Warning!!
Windows에서는 플랫폼 제한으로 인해 NestJS가 애플리케이션 종료 훅에 대해 제한적인 지원을 제공합니다. SIGINT 및 일부 경우에는 SIGBREAKSIGHUP도 작동할 수 있습니다. 그러나 SIGTERM은 Windows에서 작동하지 않습니다. 이는 프로세스 관리자에서 프로세스를 종료하는 것이 조건 없이 이루어지기 때문에 "응용 프로그램이 감지하거나 방지할 방법이 없기 때문입니다". 관련 문서를 확인하여 Windows에서 SIGINT, SIGBREAK 등이 어떻게 처리되는지 자세히 알아볼 수 있습니다. 또한 Node.js 문서의 Process Signal Events 페이지를 참조하세요.

Info
enableShutdownHooks는 리스너를 시작하여 메모리를 사용합니다. 여러 개의 Nest 앱을 단일 Node 프로세스에서 실행하는 경우 (예: Jest와 함께 병렬 테스트 실행 시), Node가 리스너 프로세스가 지나치게 많다고 알릴 수 있습니다. 이러한 이유로 enableShutdownHooks는 기본적으로 활성화되지 않습니다. 단일 Node 프로세스에서 여러 인스턴스를 실행하는 경우 이러한 조건을 염두에 두시기 바랍니다.

애플리케이션이 종료 신호를 받으면 등록된 onModuleDestroy(), beforeApplicationShutdown(), 그리고 onApplicationShutdown() 메서드들이 호출됩니다 (위에서 설명한 순서대로). 이때 매개변수로 해당 신호가 전달됩니다. 등록된 함수가 비동기 호출을 기다릴 경우 (Promise를 반환하는 경우), Nest는 프로미스가 해결되거나 거부될 때까지 순서를 진행하지 않습니다.

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

Info
app.close()를 호출하더라도 Node 프로세스를 종료하지는 않고, onModuleDestroy()onApplicationShutdown() 훅만 트리거합니다. 따라서 인터벌, 장기간 실행되는 백그라운드 작업 등이 있다면 프로세스가 자동으로 종료되지 않을 수 있습니다.

질문 및 생각

  • opt-in?
    "Opt-in"은 선택적으로 참여하거나 참여하기로 선택하는 것을 의미합니다. 즉, 특정 기능이나 동작을 사용하려면 명시적으로 그것에 참여하는 것을 의미합니다.

  • Heroku?

  • SIGINT (Interrupt Signal): 일반적으로 Ctrl + C 키를 눌러서 프로세스를 종료하는 데 사용됩니다. 이 신호가 전송되면 프로세스는 종료됩니다.
    SIGBREAK (Break Signal): 일부 운영 체제에서는 Ctrl + Break 키 조합을 사용하여 이 신호를 전송할 수 있습니다. 이것 역시 프로세스를 종료하는 데 사용됩니다.
    SIGTERM (Termination Signal): 일반적으로 운영 체제 또는 프로세스 관리자가 프로세스를 종료하고자 할 때 사용됩니다. 일반적으로 SIGINT와 유사하지만, SIGINT는 주로 사용자가 프로세스를 종료하도록 하는 데 사용되는 반면, SIGTERM은 운영 체제나 다른 프로세스가 종료하도록 하는 데 사용됩니다.

내 정리

애플리케이션의 라이프사이클 중 부트스트랩 단계 및 애플리케이션 종료의 단계에서 사용할 수 있는 이벤트 메서드들에 대한 내용이라고 이해했습니다. 특정한 시기에 내가 원하는 동작을 실행하고, 특히 애플리케이션 종료 시점에서 안전하고 유연성있게 관리할 수 있다는 점을 인상적으로 봤습니다.

Initailze할때, async를 사용해 비동기 메서드를 DB 연결과 같은 곳에 사용할 수 있다고 생각이 들었습니다.

0개의 댓글