본문으로 건너뛰기
개발 머꼬
개발 노트NestJS
hohyeon.dev13

인터페이스로 주입받으려 했더니 Nest가 의존성을 못 찾은 이유

  • #Engineering Note
  • #NestJS

문제 발생

구현을 갈아끼울 수 있게 인터페이스에 의존하도록 만들었습니다.

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() 데코레이터에 토큰을 넘깁니다.

해결 방안

  1. 토큰을 명시적으로 만듭니다. 상수로 한 곳에 둡니다.
export const PAYMENT_GATEWAY = Symbol("PAYMENT_GATEWAY");

@Injectable()
export class OrderService {
  constructor(@Inject(PAYMENT_GATEWAY) private readonly gateway: PaymentGateway) {}
}
  1. 환경에 따라 구현을 바꾸려면 useClassuseFactory입니다. 문서 설명대로 useFactoryprovider를 동적으로 만들며, 실제 provider는 팩토리 함수의 반환값으로 공급됩니다.
{
  provide: PAYMENT_GATEWAY,
  useFactory: (config: ConfigService) =>
    config.get("PAYMENT_MODE") === "live" ? new TossGateway() : new FakeGateway(),
  inject: [ConfigService],
}
  1. 상수·외부 라이브러리 인스턴스는 useValue입니다. 문서가 드는 용도 그대로 — 상수 주입, 외부 라이브러리를 Nest 컨테이너에 넣기, 그리고 실제 구현을 목 객체로 대체하기. 테스트에서 특히 쓰입니다.

  2. 다른 모듈에서 쓰려면 반드시 export합니다. 문서가 명시합니다 — 커스텀 provider를 공유하려면 토큰이나 provider 객체를 모듈의 exports에 넣어야 합니다. 이걸 빠뜨리면 "선언은 했는데 못 찾는" 같은 오류가 다시 납니다.

  3. 추상 클래스도 방법입니다. 런타임에 값으로 남으므로 토큰이 되면서 타입 역할도 합니다. 토큰 상수를 따로 관리하기 싫을 때 쓸 만합니다.

  4. 별칭이 필요하면 useExisting입니다. 같은 인스턴스를 두 이름으로 노출할 때 쓰고, 새 인스턴스를 만들지 않습니다.

공식 문서

마지막 수정

좋아요북마크

댓글0

아직 댓글이 없어요. 첫 의견을 편하게 남겨 보세요.