기술과 산업/언어 및 프레임워크

NestJS 마스터 시리즈 12화. 모듈 간 의존성 순환 문제와 해결 전략

B컷개발자 2025. 5. 9. 14:57
728x90

"Circular Dependency는 코드의 잘못이 아니라, 설계의 경고다"

NestJS에서 발생하는 순환 의존성 문제의 원인을 분석하고, forwardRef, 인터페이스 추출, 계층 재설계 등 실전에서 사용하는 해결 전략을 구조적으로 정리합니다.


순환 의존성이란 무엇인가?

A 서비스가 B 서비스를 참조하고, 동시에 B 서비스도 A를 참조할 때
NestJS는 이 구조를 **순환 의존성(Circular Dependency)**이라고 판단한다.

의존 관계 예시

A → B → A

이 구조는 NestJS의 **의존성 주입 컨테이너(DI Container)**가 인스턴스를 생성할 때 무한 루프에 빠지게 하므로, 런타임 오류를 유발할 수 있다.


실제 예시 – 사용자와 인증 서비스

// auth.service.ts
@Injectable()
export class AuthService {
  constructor(private usersService: UsersService) {}
}
// users.service.ts
@Injectable()
export class UsersService {
  constructor(private authService: AuthService) {}
}

이런 식의 교차 참조는 NestJS 실행 시 다음과 같은 오류를 발생시킨다:

Nest can't resolve dependencies of the AuthService (?). Please make sure that the argument at index [0] is available...


해결 전략 1 – forwardRef()를 활용한 해결

NestJS는 순환 의존을 해결하기 위해 forwardRef() 함수를 제공한다.

// auth.module.ts
@Module({
  providers: [AuthService],
  imports: [forwardRef(() => UsersModule)],
  exports: [AuthService],
})
export class AuthModule {}
// users.module.ts
@Module({
  providers: [UsersService],
  imports: [forwardRef(() => AuthModule)],
  exports: [UsersService],
})
export class UsersModule {}

그리고 서비스에서도 DI 시 forwardRef를 사용한다:

constructor(
  @Inject(forwardRef(() => AuthService))
  private authService: AuthService,
) {}

이 방식은 임시적인 해결책이 될 수 있으나, 구조적인 근본 문제 해결은 아니다.


해결 전략 2 – 의존성 분리 및 인터페이스 추출

가장 바람직한 방법은 양방향 의존성을 끊고 중간 계층 또는 인터페이스로 추상화하는 것이다.

  1. 공통 인터페이스 선언
  2. 의존성 주입 시 인터페이스만 사용
  3. 구현체는 실제 서비스에서 주입

이런 방식은 DDD(Domain-Driven Design)의 도메인 계층-애플리케이션 계층 분리 전략과도 유사하다.


해결 전략 3 – 기능 분리 또는 계층 재설계

순환 의존은 구조 설계가 잘못되었음을 알려주는 시그널일 수 있다.

예시:

  • UsersService는 사용자 CRUD만 담당
  • 인증 관련 로직은 AuthService에서만 관리
  • AuthService는 필요 시 UsersService의 특정 메서드만 호출
  • 그 외 역할은 AuthHelper 또는 AuthContextService로 이동

책임을 명확히 분리하면 순환은 자연스럽게 사라진다.


테스트 가능한 구조로 전환

순환 의존이 끊어진 구조는 다음과 같은 장점이 있다:

  • 서비스 단위 테스트 작성이 쉬워진다
  • 단일 책임 원칙(SRP)을 지킬 수 있다
  • 리팩터링 시 전체 시스템에 영향을 주지 않는다

마무리 인사이트

순환 의존은 단순한 NestJS의 기술 이슈가 아니라,
설계 철학의 결함을 드러내는 구조적 문제다.

  • forwardRef()는 응급처치일 뿐이다
  • 책임 분리와 계층 설계가 근본적인 해결이다
  • 장기적인 프로젝트일수록 구조적인 의존성 전략이 필요하다

"순환 의존은 기술의 문제가 아니라, 책임의 문제다"


다음 회차 예고

NestJS 마스터 시리즈 13화. TypeORM과 데이터베이스 연동 전략
PostgreSQL과의 연결 설정, Repository 패턴, 커스텀 쿼리 구성까지 실전 중심으로 정리합니다.

728x90