nest의 공식문서를 토대로 작성합니다.
순환 종속성은 두 클래스가 서로 의존할 때 발생합니다. 예를 들어 클래스 A에는 클래스 B가 필요하고 클래스 B에도 클래스 A가 필요한 경우 발생합니다. Nest에서는 모듈 간 또는 provider 간에 순환 종속성이 발생할 수 있습니다.
순환 종속성은 가능한 피해야 하지만 항상 그럴 수는 없습니다. 이러한 경우 Nest를 사용하면 두 가지 방법으로 provider 간의 순환 종속성을 해결할 수 있습니다. 하나는 정방향 참조, 다른 하나는 ModuleRef 클래스를 사용하여 DI 컨테이너에서 provider 인스턴스를 검색하는 방법이 있습니다.
그리고 모듈 간의 순환 종속성 해결 방법도 설명합니다.
WARNING
"barrel files"/index.ts 파일을 사용하여 그룹 imports를 할 때 순환 종속성이 발생할 수 있음. 모듈/프로바이더 클래스에 대해서는 barrel files를 생략해야 함. 예를 들어 barrel files와 같은 디렉터리 내의 파일을 가져올 때는 barrel files를 사용하면 안 됨. 자세한 내용은 github issue 확인.
정방향 참조를 사용하면 Nest가 forwardRef()
유틸리티 함수를 사용하여 아직 정의되지 않은 클래스를 참조할 수 있습니다. 예를 들어 CatsService
와 CommonService
가 서로 종속된 경우 관계의 양쪽에서 @Inject()
및 forwardRef()
유틸리티를 사용하여 순환 종속성을 해결할 수 있습니다. 그렇지 않으면 모든 필수 메타데이터를 사용할 수 없으므로 Nest에서 인스턴스화하지 않습니다.
예:
# cats.service.ts
@Injectable()
export class CatsService {
constructor(
@Inject(forwardRef(() => CommonService))
private commonService: CommonService,
) {}
}
# common.service.ts
@Injectable()
export class CommonService {
constructor(
@Inject(forwardRef(() => CatsService))
private catsService: CatsService,
) {}
}
Warning
인스턴스화 순서는 불확정적임. 코드가 어떤 생성자가 먼저 호출되는지에 따라 달라지지 않도록 해야 함.Scope.REQUEST
를 사용하는 provider에 순환 종속성을 가지면 정의되지 않은 종속성이 발생할 수 있음. 자세한 내용은 여기로.
forwardRef()
를 사용하는 대신 코드를 리팩토링하고 ModuleRef
클래스를 사용하여 순환 관계의 한쪽에서 provider를 검색하는 방법을 사용할 수 있습니다. ModuleRef
유틸리티 클래스에 대해 자세히 보려면 여기로.
모듈 간의 순환 종속성을 해결하려면 모듈 연결의 양쪽에서 동일한 forwardRef()
유틸리티 함수를 사용하면 됩니다.
예:
# common.module.ts
@Module({
imports: [forwardRef(() => CatsModule)],
})
export class CommonModule {}
# cats.module.ts
@Module({
imports: [forwardRef(() => CommonModule)],
})
export class CatsModule {}
이번 내용은 매우 간단하네요!! 순환 종속성 발생 시 위 두 가지 중 하나의 방법을 선택해서 적용하면 됩니다.
제가 기존 진행하던 프로젝트에서 순환 종속성을 마주하여 에러가 난 적이 있었습니다. 당시에는 이런 해결 방법이 있는 줄 모르고 그냥 파일 분리해서 수정했던 기억이 나네요!
그래서 이번에는 직접 해보겠습니다!!
기존에 있던 UserModule
과 AuthModule
에 순환 종속성을 발생시켜 보겠습니다.
# app.module.ts
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { AuthModule } from './auth/auth.module';
import { UserModule } from './user/user.module';
@Module({
imports: [AuthModule, UserModule],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}
# auth.module.ts
import { Module } from '@nestjs/common';
import { AuthController } from './auth.controller';
import { AuthService } from './auth.service';
import { UserModule } from 'src/user/user.module';
@Module({
imports: [UserModule],
controllers: [AuthController],
providers: [AuthService],
})
export class AuthModule {}
# user.module.ts
import { Module } from '@nestjs/common';
import { UserController } from './user.controller';
import { UserService } from './user.service';
import { AuthModule } from 'src/auth/auth.module';
@Module({
imports:[AuthModule],
controllers: [UserController],
providers: [UserService]
})
export class UserModule {}
이렇게 서로를 import하고 앱을 실행하면
순환 종속성이 발생한다는 에러 문구를 볼 수 있습니다.
순환 종속성이 발생하여 모듈을 인스턴스화할 수 없는 것입니다.
자 그럼 위에서 본대로 forwardRef()
를 사용하여 해결해보겠습니다.
# auth.module.ts
import { Module, forwardRef } from '@nestjs/common';
import { AuthController } from './auth.controller';
import { AuthService } from './auth.service';
import { UserModule } from 'src/user/user.module';
@Module({
imports: [forwardRef(() => UserModule)],
controllers: [AuthController],
providers: [AuthService],
})
export class AuthModule {}
# user.module.ts
import { Module, forwardRef } from '@nestjs/common';
import { UserController } from './user.controller';
import { UserService } from './user.service';
import { AuthModule } from 'src/auth/auth.module';
@Module({
imports: [forwardRef(() => AuthModule)],
controllers: [UserController],
providers: [UserService],
})
export class UserModule {}
해결!
제 코드입니다.😎
https://github.com/cxzaqq/cxzaqq-velog/tree/3.5-circular-dependency
고생하셨습니다!
다음 글에서 만나요~~😀
저도 아직 배우는 단계입니다. 지적 감사히 받겠습니다. 함께 열심히 공부해요!!