인터페이스로 주입받으려 했더니 Nest가 의존성을 못 찾은 이유
문제 발생
구현을 갈아끼울 수 있게 인터페이스에 의존하도록 만들었습니다.
export interface PaymentGateway { charge(amount: number): Promise<void>; }
@Injectable()
export class OrderService {
constructor(private readonly gateway: PaymentGateway) {} // 실패
}Nest can't resolve dependencies of the OrderService (?).
Please make sure that the argument dependency at index [0] is available.원인 분석
인터페이스는 런타임에 존재하지 않습니다. NestJS 문서가 이유를 그대로 적습니다 — TypeScript 타입/인터페이스는 컴파일 과정에서 지워지므로 Nest가 런타임에 참조할 수 없습니다.
Nest의 기본 DI는 생성자 파라미터의 클래스 자체를 토큰으로 씁니다. PaymentGateway가 클래스라면 그 클래스가 토큰이지만, 인터페이스는 컴파일되면 아무것도 남지 않아 토큰이 될 수 없습니다.
그래서 문서는 클래스가 아닌 토큰을 안내합니다 — 문자열이나 심볼을 토큰으로 쓸 수 있고, 그렇게 주입할 때는 @Inject() 데코레이터에 토큰을 넘깁니다.
해결 방안
- 토큰을 명시적으로 만듭니다. 상수로 한 곳에 둡니다.
export const PAYMENT_GATEWAY = Symbol("PAYMENT_GATEWAY");
@Injectable()
export class OrderService {
constructor(@Inject(PAYMENT_GATEWAY) private readonly gateway: PaymentGateway) {}
}- 환경에 따라 구현을 바꾸려면
useClass나useFactory입니다. 문서 설명대로useFactory는 provider를 동적으로 만들며, 실제 provider는 팩토리 함수의 반환값으로 공급됩니다.
{
provide: PAYMENT_GATEWAY,
useFactory: (config: ConfigService) =>
config.get("PAYMENT_MODE") === "live" ? new TossGateway() : new FakeGateway(),
inject: [ConfigService],
}-
상수·외부 라이브러리 인스턴스는
useValue입니다. 문서가 드는 용도 그대로 — 상수 주입, 외부 라이브러리를 Nest 컨테이너에 넣기, 그리고 실제 구현을 목 객체로 대체하기. 테스트에서 특히 쓰입니다. -
다른 모듈에서 쓰려면 반드시 export합니다. 문서가 명시합니다 — 커스텀 provider를 공유하려면 토큰이나 provider 객체를 모듈의
exports에 넣어야 합니다. 이걸 빠뜨리면 "선언은 했는데 못 찾는" 같은 오류가 다시 납니다. -
추상 클래스도 방법입니다. 런타임에 값으로 남으므로 토큰이 되면서 타입 역할도 합니다. 토큰 상수를 따로 관리하기 싫을 때 쓸 만합니다.
-
별칭이 필요하면
useExisting입니다. 같은 인스턴스를 두 이름으로 노출할 때 쓰고, 새 인스턴스를 만들지 않습니다.
댓글0
댓글을 남기려면 로그인이 필요해요. 로그인
아직 댓글이 없어요. 첫 의견을 편하게 남겨 보세요.