[NestJS 공식문서 정독하기] Fundamentals - Injection scopes
2022. 8. 9. 19:36ㆍWeb/NestJS
반응형
🔗 https://docs.nestjs.com/fundamentals/injection-scopes
- Node.js는 다른 웹 프레임워크와는 다르게 멀티 쓰레드 상태 비저장(Multi-Threaded Stateless) 모델을 따르지 않음
- 따라서, 싱글톤 인스턴스를 사용하는 것은 안전한 방식임
- 이는 요청으로 들어오는 모든 정보(DB 커넥션 풀, 전역 싱글톤 서비스 등)들을 공유할 수 있다는 것을 의미함
- 하지만, 요청 단위 생명주기가 필요한 예외 케이스들이 존재함 (ex. 요청별 캐싱, 요청 추적, 멀티테넌시)
- injection scope는 원하는 생명주기를 적용할 수 있는 방법을 제공함
Provider scope
- provider는 아래의 scope들을 가질 수 있음
DEFAULT
: Singleton. 애플리케이션 전체에 하나의 인스턴스가 공유됨. 애플리케이션의 생명주기와 동일함.REQUEST
: 각 요청 당 하나의 새로운 인스턴스가 생성됨. 요청 처리가 완료된 이후에는 GC됨.TRANSIENT
: 공유되지 않음. provider를 사용하는 consumer는 전용 인스턴스를 공급받음.
- 대부분의 경우에는 singleton scope를 사용하는 것이 권장됨
Usage
import { Injectable, Scope } from '@nestjs/common';
@Injectable({ scope: Scope.REQUEST })
export class CatsService {}
{
provide: 'CACHE_MANAGER',
useClass: CacheManager,
scope: Scope.TRANSIENT,
}
@nestjs/common
의Scope
enum을 사용@Injectable()
decorator의 scope 속성에 명시해주면 됨- custom provider의 경우는 long-hand form의 scope 속성에 명시해주면 됨
Controller scope
@Controller({
path: 'cats',
scope: Scope.REQUEST,
})
export class CatsController {}
- provider scope와 기능은 동일
ControllerOptions
객체의scope
속성에 명시
Scope hierarchy
- 연관된 컴포넌트들이 서로 다른 scope를 가지게 될 때를 주의해야 함
- 예를 들어,
CatsController <- CatsService <- CatsRepository
순으로 의존성을 가질 때,CatsService
만 request scope여도CatsController
는CatsService
에 의존하기 때문에 마찬가지로 request-scoped가 됨 - 하지만, singleton인
DogsService
가 있고 transient-scoped인LoggerService
에 의존하는 경우에는 scope가 전이되지 않음
Request provider
import { Injectable, Scope, Inject } from '@nestjs/common';
import { REQUEST } from '@nestjs/core';
import { Request } from 'express';
@Injectable({ scope: Scope.REQUEST })
export class CatsService {
constructor(@Inject(REQUEST) private request: Request) {}
}
import { Injectable, Scope, Inject } from '@nestjs/common';
import { CONTEXT } from '@nestjs/graphql';
@Injectable({ scope: Scope.REQUEST })
export class CatsService {
constructor(@Inject(CONTEXT) private context) {}
}
- HTTP 서버 기반 애플리케이션의 request-scoped provider를 사용할 경우
REQUEST
객체를 주입 받아서 원본 요청 객체에 접근할 수 있음 - Microservice나 GraphQL 애플리케이션에서는
REQUEST
대신CONTEXT
를 주입 받을 수 있음
Inquirer provider
import { Inject, Injectable, Scope } from '@nestjs/common';
import { INQUIRER } from '@nestjs/core';
@Injectable({ scope: Scope.TRANSIENT })
export class HelloService {
constructor(@Inject(INQUIRER) private parentClass: object) {}
sayHello(message: string) {
console.log(`${this.parentClass?.constructor?.name}: ${message}`);
}
}
INQUIRER
토큰으로 provider가 생성된 클래스를 주입 받을 수 있음
Performance
- request-scoped를 사용하면 NestJS가 메타데이터를 최대한 캐싱하려고 하더라도 매 요청마다 새로운 인스턴스를 생성하므로 전체적인 응답 시간과 벤치마크 결과에 부정적인 영향을 끼침
- 따라서, 꼭 request-scoped일 필요가 있지 않은 경우는 기본 singleton scope를 활용하는 것을 강력 권장
- 잘 설계된 애플리케이션은 request-scoped provider를 활용하더라도 지연 시간이 최대 5% 이상 느려지지 않아야 함
Durable providers
import {
HostComponentInfo,
ContextId,
ContextIdFactory,
ContextIdStrategy,
} from '@nestjs/core';
import { Request } from 'express';
const tenants = new Map<string, ContextId>();
export class AggregateByTenantContextIdStrategy implements ContextIdStrategy {
attach(contextId: ContextId, request: Request) {
const tenantId = request.headers['x-tenant-id'] as string;
let tenantSubTreeId: ContextId;
if (tenants.has(tenantId)) {
tenantSubTreeId = tenants.get(tenantId);
} else {
tenantSubTreeId = ContextIdFactory.create();
tenants.set(tenantId, tenantSubTreeId);
}
// If tree is not durable, return the original "contextId" object
return (info: HostComponentInfo) =>
info.isTreeDurable ? tenantSubTreeId : contextId;
}
}
ContextIdFactory.apply(new AggregateByTenantContextIdStrategy());
- 매 요청마다 DI tree를 생성하지 말고 대신 DI sub-tree를 생성해서 공유 (provider가 요청의 UUID같은 고유값에 의존하는게 아니고, 특정 기준에 따라 provider를 분류할 수 있다면 매번 새로운 DI sub-tree를 생성할 이유가 없음)
- 다만, 분류의 수가 굉장히 많은 경우에는 적합하지 않은 기능임
- request scope와 비슷하게 A가
durable
인 B를 의존하고 있다면 A 또한 암묵적으로durable
이 됨 (명시적으로 false로 지정할 수 있음)
import { Injectable, Scope } from '@nestjs/common';
@Injectable({ scope: Scope.REQUEST, durable: true })
export class CatsService {}
{
provide: 'CONNECTION_POOL',
useFactory: () => { ... },
scope: Scope.REQUEST,
durable: true,
}
- 위와 같이 일반 provider와 custom provider를
durable
로 만들 수 있음
반응형