NestJS DI 동작 단계별 흐름
- ApplicationConfig 초기화
- 부트스트랩 시 ApplicationConfig가 먼저 생성됩니다.
- 글로벌 미들웨어, 파이프, 인터셉터 등 전역 설정을 담당합니다.
- 이후 DI Container가 생성되어 의존성 그래프 구축이 시작됩니다.
- 모듈 스캔 (Dependency Scanner)
- 모든 모듈 내 프로바이더, 컨트롤러, 익스포트를 스캔합니다.
- 각 provider의 토큰을 계산해 Container에 등록합니다.
- forwardRef를 활용해 순환 참조 가능성을 미리 추적합니다.
- InstanceWrapper로 상태 관리
- 각 의존성은 InstanceWrapper로 감싸집니다.
- 상태: isPending(생성 중), isResolved(완료), isRejected(실패)를 관리합니다.
- isPending 상태로 순환 참조를 감지하며, Proxy 기반의 Lazy Resolution으로 무한 루프 방지합니다.
- Injector의 역할
- 단순 주입이 아니라 인스턴스 생성 → 주입 → 라이프사이클 훅(@OnModuleInit) 호출까지 담당합니다.
- 생성자 파라미터 의존성 주입
- Reflect.getMetadata()로 생성자 파라미터 타입을 조회합니다.
- 타입을 Provider Token으로 사용해 Container에서 의존성을 찾아 생성자에 주입합니다.
- 이를 통해 TypeScript의 타입 정보를 런타임에 활용합니다.
NestJS 부트스트랩 시, ApplicationConfig가 가장 먼저 생성되어 글로벌 미들웨어, 파이프, 인터셉터 등의 전역 설정을 담당합니다. 이후 DI Container가 생성되어, 본격적인 의존성 그래프 구축과 관리를 시작합니다.
Dependency Scanner는 각 모듈의 모든 프로바이더, 컨트롤러, 익스포트를 스캔하며, provider token을 계산해 Container에 등록합니다. 이 과정에서 forwardRef를 활용해 순환 참조가 예상되는 토큰도 미리 추적하여 문제를 예방합니다.
각 의존성은 InstanceWrapper로 감싸지며, 이 객체는 인스턴스 생성 상태를 isPending(생성 중), isResolved(완료), isRejected(실패)로 관리합니다. 순환 참조는 isPending 상태를 토대로 감지되며, Proxy를 이용한 Lazy Resolution으로 무한 루프를 방지합니다.
Injector는 단순 주입에 그치지 않고, 인스턴스 생성부터 주입, 그리고 @OnModuleInit 같은 라이프사이클 훅 호출까지 담당하여 객체 초기화 과정을 체계적으로 관리합니다.
주입 시, Injector는 Reflect.getMetadata()를 통해 생성자 파라미터 타입 정보를 얻고, 이 타입을 Provider Token으로 사용하여 Container에서 필요한 의존성을 찾아 생성자 인자로 주입합니다. 이를 통해 타입스크립트의 정적 타입 정보를 런타임에 효과적으로 활용할 수 있습니다.
왜 Nest는 DI에서 reflect-metadata를 사용하나요?
- TypeScript 컴파일 특성
- 컴파일 시 타입 정보 완전 제거 → 컴파일 타임 DI 그래프 구성 불가
- reflect-metadata 역할
- 생성자 파라미터 타입을 런타임에 메타데이터로 저장 (design:paramtypes)
- Reflect.getMetadata('design:paramtypes', 클래스)로 타입 배열 획득
- DI 그래프 생성 과정
- 런타임에 모듈 스캔 후 reflect-metadata로 의존성 타입 확인
- 타입 정보를 바탕으로 의존성 그래프 동적 구축
- 스코프 결정은 별도 처리
- Singleton, Request, Transient Scope는 @Injectable({ scope }) 등 명시적 설정
- reflect-metadata는 타입 정보만 제공
- 런타임 DI의 구조적 이유
- TypeScript 특성상 정적 타입 유지 어려움 → 런타임 DI 필수
- Nest는 이를 극복하기 위해 reflect-metadata 활용
TypeScript는 컴파일러(tsc)를 통해 JavaScript로 변환될 때 모든 타입 정보가 제거됩니다. 이 때문에 NestJS는 어떤 타입의 객체를 생성할 수 있습니다.
reflect-metadata는 컴파일 시점에 데코레이터가 적용된 클래스 생성자의 파라미터 타입을 design:paramtypes라는 키로 메타데이터에 저장합니다. 예를 들어, @Injectable() 데코레이터가 붙은 클래스 A가 생성자에 B와 C 타입을 받는다면, Reflect.getMetadata('design:paramtypes', A) 호출 시 [B, C] 배열을 반환합니다. Nest는 이 메타데이터를 바탕으로 “A를 생성하기 위해 B와 C가 필요하다”는 사실을 파악해 런타임에 의존성 주입 그래프를 만듭니다.
Java(Spring)의 경우 JVM 바이트코드에 타입 정보가 유지되어 컴파일 타임에 DI 그래프를 구성할 수 있지만, TypeScript는 타입이 완전히 사라지기 때문에 Nest는 런타임에 모듈을 스캔하고 reflect-metadata로 타입 정보를 읽어서 DI 그래프를 동적으로 생성합니다. 이 점이 Nest DI가 근본적으로 런타임 DI인 이유이며, TypeScript 기반 IoC 컨테이너가 갖는 구조적 한계라고 할 수 있습니다.
한편, Singleton, Request, Transient와 같은 프로바이더 스코프는 reflect-metadata로 결정되지 않고, Nest 내부 설정에서 따로 관리됩니다. 예를 들어 @Injectable({ scope: Scope.REQUEST })와 같이 명시적으로 설정하는 방식입니다. 따라서 reflect-metadata는 오직 타입 정보를 파악하는 역할만 하며, 스코프 결정은 별도의 메커니즘으로 처리됩니다.
Nest의 InstanceWrapper 객체
- InstanceWrapper의 역할
- Nest Container 내에서 각 Provider 인스턴스의 생성 상태를 추적하고 관리하는 핵심 객체.
- 중요 내부 상태
- instance: 생성된 실제 인스턴스
- isResolved: 인스턴스 생성 완료 여부
- isPending: 현재 인스턴스 생성 중 여부 (순환 의존성 탐지에 사용)
- metatype: 생성할 클래스 타입 정보
- inject: 주입할 커스텀 의존성 목록
- scope: Provider의 스코프 (Singleton, Request, Transient 등)
- 상태 관리 필요성
- 런타임 DI 시 재귀적으로 의존성 그래프를 탐색하는데, isPending 상태를 통해 순환 참조를 실시간으로 감지.
예: A → B → A 순환 의존 시 isPending이 이미 true인 A를 다시 요청하면 순환 감지.
- Lazy Resolution(Proxy) 활용
- 순환 참조를 완전히 차단하면 설계가 불가능할 수 있어, Proxy를 이용해 빈 객체를 먼저 주입하고 이후 실제 객체 호출로 대체함.
InstanceWrapper는 Nest의 DI 컨테이너 내부에서 각 프로바이더 인스턴스가 어떻게 생성되고 관리되는지를 추적하는 핵심 구조체입니다. 이 객체는 생성된 실제 인스턴스(instance), 생성 완료 여부(isResolved), 그리고 현재 인스턴스 생성 중인지를 나타내는 상태(isPending) 등을 가지고 있습니다. 특히 isPending 상태는 런타임에 의존성 그래프를 재귀적으로 탐색하는 과정에서 순환 의존성을 감지하는 중요한 역할을 합니다.
예를 들어, A가 B에 의존하고 B가 다시 A에 의존하는 순환 참조가 발생할 때, A의 InstanceWrapper가 생성 중(isPending이 true)인 상태에서 B가 A를 다시 요청하면 이를 즉시 탐지할 수 있습니다. 이로써 Nest는 순환 의존성 문제를 방지하거나, 필요 시 Proxy 객체를 활용한 Lazy Resolution으로 의존성 주입을 안전하게 처리합니다.
또한 InstanceWrapper는 생성할 클래스 타입(metatype), 주입해야 할 의존성(inject), 그리고 스코프 정보(scope)도 관리하여, 각 프로바이더가 올바르게 생성되고 적절한 라이프사이클을 갖도록 지원합니다. 이런 상태 관리와 감지 기능 덕분에 NestJS는 TypeScript 환경에서도 안정적인 런타임 DI를 구현할 수 있습니다.
ModuleRef의 역할과 exports -> imports -> provider 공유
- ModuleRef의 정확한 역할
- ModuleRef는 Nest 내부 DI Container의 "핸들러" 역할입니다.
- Nest 내부 Container는 여러 Module 객체를 관리합니다.
- 각 Module 안에 providers, controllers, exports, imports 등이 존재합니다.
- 이때 한 Module 안에서 "내부 의존성 탐색"을 도와주는 것이 ModuleRef입니다.
Why가 핵심
- Nest는 하나의 Container가 모든 모듈을 통으로 관리합니다.
- 하지만 실제 의존성 주입 시에는 "현재 모듈 기준"으로 먼저 탐색 → 외부 모듈로 확장됩니다.
- ModuleRef는 현재 모듈 스코프 내에서 의존성을 찾고 관리하기 위한 API 레이어입니다.
- 실질적인 내부 Container는 InternalCoreModule과 Container가 담당하고, ModuleRef는 이를 감싸는 사용자 접근 레이어 역할을 합니다.
Container = DI 시스템 전체의 메모리 구조
ModuleRef = 현재 모듈에서 provider를 resolve하는 도우미
- exports → imports → provider 공유 흐름
이 부분이 핵심입니다. 이것을 아키텍처 레벨로 정리합니다:
(1) imports: [OtherModule] 의존하는 모듈을 명시
(2) OtherModule이 exports: [SomeProvider] 외부 모듈에 내 provider를 노출
(3) Dependency Scanner가 imports를 순회 전체 Dependency Tree 탐색
(4) Container가 exports된 provider를 읽어옴 의존성 충족
(5) 최종적으로 주입 준비 provider graph 완성
핵심 규칙
- exports는 "다른 모듈이 접근 가능한 Provider 목록"이다.
- imports는 "내가 사용할 모듈을 불러온다"는 의미다.
- Nest는 항상 exports → imports 체인을 먼저 해결한 후에 DI를 수행합니다.
- exports되지 않은 provider는 외부 모듈에서 절대 사용할 수 없습니다.
- 왜 재귀 구조인가?
- Nest는 재귀 순회를 통해 Dependency Tree를 그립니다.
- imports의 깊이가 무한히 중첩될 수 있기 때문입니다.
- 이를 Dependency Scanner → Container 등록 → InstanceWrapper 생성 → 재귀적 Injector 호출 흐름으로 관리합니다.
정리
NestJS 내부에서 ModuleRef는 DI 컨테이너의 핵심적인 “핸들러” 역할을 합니다. NestJS는 여러 개의 모듈을 하나의 컨테이너 안에서 관리하는데, 각 모듈은 자신만의 프로바이더, 컨트롤러, 익스포트, 임포트 등을 포함합니다. 이때 ModuleRef는 “현재 모듈 기준”으로 의존성을 탐색하고 관리하는 API 레이어로서, 내부 DI 컨테이너가 제공하는 복잡한 기능들을 좀 더 쉽게 다룰 수 있도록 돕습니다. 즉, 전체 DI 시스템인 컨테이너가 모든 모듈을 통째로 관리하는 메모리 구조라면, ModuleRef는 특정 모듈 내부에서 프로바이더를 해결하고 주입하는 데 집중하는 도우미 역할을 수행한다고 할 수 있습니다. 실제 내부 컨테이너 기능은 InternalCoreModule과 컨테이너가 담당하며, ModuleRef는 이를 감싸서 사용자 코드가 접근할 수 있게 합니다.
한편, NestJS의 모듈 간 의존성 공유 흐름은 exports와 imports의 관계를 통해 엄격하게 관리됩니다. 한 모듈이 다른 모듈을 imports할 때, 이는 해당 모듈에 명시적으로 의존한다는 뜻입니다. 반대로 exports는 자신이 외부 모듈에 공개하는 프로바이더 목록입니다. NestJS는 이 관계를 따라 의존성 트리를 순회하는데, Dependency Scanner가 임포트된 모듈들을 탐색하고, 컨테이너는 exports된 프로바이더를 찾아 의존성을 충족시킵니다. 이렇게 해서 최종적으로 프로바이더 그래프가 완성되어 주입이 이뤄집니다. 중요한 점은, exports되지 않은 프로바이더는 외부 모듈에서 사용할 수 없다는 규칙으로 모듈 간 경계를 명확히 구분한다는 것입니다.
마지막으로 NestJS가 이 의존성 트리를 재귀적으로 순회하는 이유는, 모듈 임포트가 무한히 중첩될 수 있기 때문입니다. 이 재귀 구조는 Dependency Scanner가 모듈을 순회하고, 컨테이너에 등록하며, InstanceWrapper를 생성하고, Injector가 재귀적으로 호출되어 의존성을 해결하는 흐름으로 관리됩니다. 따라서 NestJS의 DI 시스템은 이러한 재귀적 탐색과 등록 과정을 통해 복잡한 의존성 구조를 체계적으로 처리합니다.
Nest의 LifeCycle Hook
- onModuleInit()
- 각 모듈 내 프로바이더 인스턴스 생성 후 호출
- 의존성 주입 완료 직후 초기화 용도
- onApplicationBootstrap()
- 모든 모듈의 onModuleInit() 완료 후 호출
- 애플리케이션 전체 초기화 완료 알림
- onModuleDestroy()
- 각 모듈 내 프로바이더가 파괴될 때 호출
- 모듈 단위 자원 해제 담당
- onApplicationShutdown()
- 애플리케이션 종료 시 (SIGINT, SIGTERM 등) 호출
- 전역 자원(DB 커넥션, Redis 등) 정리
- 훅 호출 순서
- DI 완료 및 인스턴스 생성
- 각 프로바이더 onModuleInit() 호출
- 모든 모듈 완료 → onApplicationBootstrap() 호출
- 정상 종료 시 onModuleDestroy() → onApplicationShutdown() 호출
- 설계 의도
- DI 주입 안전성 확보
- 모듈별 vs 애플리케이션 전체 자원 관리 책임 분리
정리
NestJS의 라이프사이클 훅은 DI 컨테이너의 상태와 밀접하게 연동되어 있습니다. 먼저, Dependency Scanner가 모든 모듈과 프로바이더를 등록하여 의존성 그래프를 완성합니다. 그 후 의존성 주입이 완료되어 인스턴스가 생성되면, 각 프로바이더는 onModuleInit() 훅을 실행합니다. 이 훅은 해당 모듈 내에서 의존성이 주입된 직후 초기화 작업을 수행하기 위한 용도로 존재합니다.
모든 모듈의 onModuleInit()이 완료되면, 애플리케이션 전체 초기화가 끝났음을 알리는 onApplicationBootstrap()이 호출됩니다. 이 시점에서는 DI 그래프가 완전히 구축되어 있으며, 시스템이 정상 가동할 준비가 완료된 상태입니다.
종료 과정에서는 먼저 각 모듈 단위에서 자원을 해제하는 onModuleDestroy() 훅이 실행되고, 이어서 데이터베이스 커넥션 풀이나 Redis 등 글로벌 자원을 정리하는 onApplicationShutdown() 훅이 호출됩니다. 이와 같이 훅 호출 순서를 명확히 분리한 이유는 모듈별 리소스 관리와 애플리케이션 전역 자원 관리를 구분하여 안정적인 종료를 보장하기 위해서입니다.
요약하면, NestJS는 DI 주입 후 인스턴스 초기화, 애플리케이션 초기화 완료, 모듈별 종료, 애플리케이션 종료의 순서로 라이프사이클 훅을 호출하여 자원 관리를 체계적으로 수행합니다.
forwardRef()가 필요한 상황
- 순환 참조 문제
- 타입 기반 생성자 주입 시 A → B → A 의존성 순환 발생 가능
- forwardRef 역할
- 의존성 토큰 평가를 즉시 하지 않고, 콜백 함수로 지연 실행
- Dependency Scanner가 발견 시 ProxyToken으로 감싸 등록
- 동작 방식
- DI 그래프 완성 후 ProxyToken 내부 콜백 실행 → 실제 클래스 해석 및 주입 완료
- 설계 이유
- TypeScript 타입 소거 및 import 순서 문제로 순환 해소 어려움
- 지연된 토큰 해소로 DI 자동화 안정성 극대화
- forwardRef()가 왜 필요한가? (내부 DI 입장에서)
- Nest의 DI Container는 타입 기반 Constructor Injection을 하기 위해 생성자 파라미터 타입정보를 기반으로 Provider 인스턴스를 찾습니다.
- 순환참조 상황에서는 A가 B를 의존하고 B가 다시 A를 의존하는데 → 이 때 둘 다 아직 완전히 생성되지 않았기 때문에 InstanceWrapper가 isPending 상태에서 재귀 진입합니다.
- Nest는 일반적으로 Dependency Graph를 한 번에 완전 탐색하려고 시도합니다.
- forwardRef()가 내부에서 어떻게 동작하는가?
- forwardRef()는 결국 Provider Token Resolution을 Lazy하게 미룹니다.
- forwardRef는 함수를 받아서 콜백으로 지연 실행하도록 합니다.
- 즉, Nest가 Dependency Scanner 시점에 AService 토큰을 등록할 때 아직 BService가 완전히 등록되지 않았더라도, 토큰 참조만 미리 기록하고 나중에 의존성 재귀를 마칠 때 최종적으로 완성됩니다.
- 결국 내부적으로 Token Resolver가 typeof forwardRef인지 확인하고, 이를 함수 실행으로 Lazy하게 실제 Class를 얻어옵니다.
- Why 이렇게 설계했는가?
- 타입스크립트 환경에서는 클래스 선언 순서나 import 순서만으로 순환을 해소하기가 매우 어려움
- Nest가 의존성 주입 책임을 프레임워크가 맡기 위해서 Token Resolution 레이어를 추가한 것
- forwardRef는 "지연된 토큰 해소" 전략으로 순환참조 문제를 명시적 허용하게 설계
정리
NestJS의 DI 시스템은 타입 기반 생성자 주입(Constructor Injection) 방식을 사용하기 때문에, 의존성 간 순환 참조가 발생하면 Token Resolution 단계에서 문제가 발생합니다. 예를 들어, A가 B를 의존하고, B가 다시 A를 의존하는 경우, 두 프로바이더가 완전히 생성되기 전에 서로를 참조하는 상황이 생깁니다. 이 문제를 해결하기 위해 NestJS는 forwardRef() 함수를 제공합니다.
forwardRef()는 의존성 토큰을 즉시 평가하지 않고, 콜백 함수 형태로 지연 평가하는 방식입니다. Nest의 Dependency Scanner가 모듈을 스캔할 때 forwardRef를 만나면, 해당 토큰을 ProxyToken으로 감싸서 일단 등록해 둡니다. 이후 전체 DI 그래프가 완성될 때, 이 ProxyToken 내부에 저장된 콜백 함수를 실행해 실제 클래스를 동적으로 해석하고 의존성을 완성합니다.
이와 같은 설계는 TypeScript가 컴파일 시 타입 정보를 제거하고, import 순서로는 순환 문제를 쉽게 해결할 수 없는 환경에서 매우 중요합니다. NestJS가 DI 토큰 해석을 지연시킴으로써, 순환 참조 문제를 명시적으로 허용하고 안정적인 런타임 DI 자동화를 보장하는 핵심 메커니즘이라고 할 수 있습니다.
Exports와 Imports의 동작 순서
- Exports가 무엇인가?
- Exports는 모듈이 외부에 공개하는 프로바이더 목록을 정의합니다.
- 모듈 내에서 exports에 포함된 프로바이더만 외부 모듈에서 접근할 수 있습니다.
- Imports가 무엇인가?
- Imports는 다른 모듈의 exports를 참조하여 의존성을 확보하는 역할을 합니다.
- 모듈이 의존하는 외부 모듈을 명시하고, 해당 모듈에서 노출한 프로바이더를 사용합니다.
- Nest가 의존성 그래프를 구축하는 순서
- 먼저 각 모듈의 exports를 스캔하여 외부에 공개되는 프로바이더를 파악합니다.
- 그 다음, imports를 통해 외부 모듈의 exports를 참조하면서 의존성 트리를 확장합니다.
- 마지막으로, 현재 모듈의 providers를 그래프에 연결해 최종 의존성 주입을 완성합니다.
- exports를 생략하면 무슨 일이 발생하는가?
- exports를 등록하지 않은 프로바이더는 외부 모듈에서 접근할 수 없습니다.
- 이로 인해 DI 주입 시 Nest can't resolve dependencies와 같은 에러가 발생합니다.
- Nest는 모듈 경계와 캡슐화를 엄격히 지켜, 의도치 않은 프로바이더 노출을 방지합니다.
- 왜 이런 구조를 사용하는가?
- 모듈 간 경계 강화를 통해 캡슐화와 충돌 방지를 실현합니다.
- 이는 대형 서비스에서 모듈 독립성, 확장성, 테스트 용이성을 보장하기 위한 설계입니다.
정리
NestJS에서 의존성 그래프를 그릴 때 exports와 imports의 동작 순서는 매우 중요합니다. 먼저 exports는 해당 모듈이 외부에 어떤 프로바이더를 노출할지를 명시하는 역할을 합니다. 모듈은 자신만의 스코프 내에서 프로바이더를 관리하는데, 외부 모듈에서 이 프로바이더에 접근하려면 반드시 exports에 등록되어 있어야 합니다. 그렇지 않으면 외부 모듈은 그 프로바이더를 볼 수 없고, DI 그래프에서 연결되지 않아 의존성 주입 오류가 발생합니다.
Nest는 DI Graph를 그릴 때, 먼저 exports를 스캔하여 각 모듈이 외부에 노출하는 provider 토큰을 파악합니다. 그 후 imports를 통해 의존성을 연결하면서 provider를 매칭합니다. 이렇게 하는 이유는 Nest가 모듈 스코프를 강하게 유지하기 때문이며, exports를 통해 의도적으로 외부에 노출할 provider를 선언해야만 다른 모듈에서 해당 provider를 주입받을 수 있습니다. exports를 생략할 경우 해당 provider는 오직 자기 모듈 내부에서만 사용 가능하며, 의존성 주입 시 토큰 해석에 실패하여 DI Resolution 에러가 발생합니다. Nest가 이런 구조를 택한 이유는 모듈 간 캡슐화, 토큰 충돌 방지, 대규모 시스템 확장성을 확보하기 위함입니다.
이러한 구조는 모듈 간 충돌을 줄이고, 서비스 확장성과 도메인 독립성을 높이며, 테스트 용이성을 향상시키기 위한 설계 선택입니다. 즉, exports는 모듈의 공개 API 역할을 하며, 이를 통해 모듈 간 의존성이 명확하고 안전하게 관리됩니다.
Nest의 DI System이 Singleton, Request, Transient Scope
- Singleton Scope
- 애플리케이션 전역에서 인스턴스 하나만 공유
- 메모리·CPU 효율적 관리
- Request Scope & ContextId
- 요청마다 별도 인스턴스 필요
- ContextId(UUID)로 요청 컨텍스트 구분
- Map<Token, Map<ContextId, InstanceWrapper>> 구조로 관리
- ContextId 없으면 Request Scope 불가능
- Request Scope 메모리 관리
- 요청별 다수 인스턴스 생성 → 메모리 부담 가능성
- Injector가 요청 종료 시점에 명시적 인스턴스 정리 수행
- 정리 실패 시 메모리 릭, OOM 발생
- Transient Scope
- 매번 새 인스턴스 생성 (Factory 패턴 유사)
- 주로 유틸성 객체, 특정 상태 분리용
- 일반 서비스는 대부분 Singleton/Request 사용
- 설계 의도
- 확장성 높은 DI 아키텍처 구현
- 다양한 컨텍스트 지원 및 상태 격리 가능
- Spring 대비 더 유연하고 동적인 구조
정리
NestJS는 DI 시스템에서 세 가지 주요 스코프를 지원합니다: Singleton, Request, 그리고 Transient입니다. 기본적으로 Singleton은 애플리케이션 전역에서 하나의 인스턴스를 공유하여 메모리와 CPU 자원을 효율적으로 사용하는 방식입니다. 반면, Request 스코프는 HTTP 요청 등 각각의 요청마다 별도의 인스턴스를 생성해 상태를 격리해야 할 때 사용합니다. 이때 Nest는 내부적으로 UUID 기반의 ContextId를 활용해 각 요청 컨텍스트를 구분합니다.
ContextId는 단일한 전역 컨테이너에 하나의 인스턴스를 저장하는 기존 구조로는 불가능한 요청별 인스턴스 격리를 가능하게 합니다. DI 컨테이너는 Map<Token, Map<ContextId, InstanceWrapper>> 구조로 토큰별로 여러 요청 컨텍스트를 구분해 관리하며, 이를 통해 요청마다 다른 인스턴스를 주입할 수 있습니다. 이처럼 ContextId가 없다면 Request 스코프는 실질적으로 성립할 수 없습니다.
하지만 Request 스코프는 매 요청마다 새 인스턴스를 생성하기 때문에 트래픽이 많은 환경에서는 많은 인스턴스가 단기간 생성되고 소멸됩니다. Nest는 내부적으로 가비지 컬렉션에만 의존하지 않고, 요청 종료 시점에 Injector가 관련 인스턴스들을 명시적으로 정리해 메모리 누수를 방지합니다. 이 정리가 제대로 이루어지지 않으면 메모리 릭이나 OOM 문제가 자주 발생할 수 있습니다.
Transient 스코프는 매번 새로운 인스턴스를 생성하는 방식으로 거의 팩토리 패턴과 유사합니다. 일반 서비스 로직에서는 Singleton이나 Request 스코프로 충분하며, Transient는 주로 상태가 완전히 독립적이어야 하는 유틸리티 객체(예: 포매터, 파서)나 특정한 상황에서만 사용됩니다. 또한 부모-자식 스코프 전파를 차단하고 싶을 때 선택되기도 합니다.
이런 다중 스코프 체계는 Nest가 다양한 서비스 컨텍스트를 지원하며 확장성 높은 DI 시스템을 구현하기 위해 설계한 것입니다. 복잡한 비즈니스 요구를 처리하기 위해 ContextId 기반 멀티 스코프 시스템을 도입함으로써, Spring과 비교해 더 유연하고 동적인 DI 환경을 제공합니다.
'Programming > Nest.js' 카테고리의 다른 글
| [번역] Pipes란 무엇인가? (0) | 2024.08.31 |
|---|---|
| [번역] Passport란 무엇인가? (0) | 2024.08.31 |
| [번역] Guard란 무엇인가? (0) | 2024.08.29 |