서로를 주입하는 두 서비스 때문에 Nest가 뜨지 않은 이유
문제 발생
UserService가 AuthService를, AuthService가 다시 UserService를 쓰게 되자 애플리케이션이 뜨지 않았습니다.
Nest can't resolve dependencies of the UserService (?).
Please make sure that the argument AuthService at index [0]
is available in the UserModule context.주입은 됐는데 undefined인 경우도 있었습니다.
원인 분석
순환 의존입니다. 공식 문서의 정의 그대로 클래스 A가 클래스 B를 필요로 하고, 클래스 B도 클래스 A를 필요로 하는 상황입니다. Nest는 어느 쪽을 먼저 만들어야 할지 결정할 수 없습니다.
문서는 실패 이유도 적어 둡니다 — 이 상태에서는 필요한 메타데이터가 모두 준비되지 않기 때문에 인스턴스를 만들지 못합니다. 그래서 오류 메시지가 "resolve dependencies"로 나오거나, 한쪽이 undefined로 주입된 채 런타임에야 터집니다.
배럴 파일도 같은 문제를 만듭니다. 문서가 따로 경고합니다 — 같은 디렉터리에서 import할 때 index.ts를 거치면 의도치 않은 순환 참조가 생길 수 있습니다.
해결 방안
- 먼저 설계를 의심합니다. 문서의 권고는 분명합니다 — 순환 의존은 가능하면 피해야 합니다. 서로를 부르는 두 서비스는 대개 "둘 다 필요로 하는 세 번째 것"이 아직 분리되지 않은 상태입니다.
UserService ─┐
├─> TokenService (공통 부분을 뺀다)
AuthService ─┘- 정말 필요하면
forwardRef()를 양쪽에 겁니다. 한쪽만 걸면 해결되지 않습니다.
@Injectable()
export class UserService {
constructor(
@Inject(forwardRef(() => AuthService))
private authService: AuthService,
) {}
}@Injectable()
export class AuthService {
constructor(
@Inject(forwardRef(() => UserService))
private userService: UserService,
) {}
}- 모듈 사이의 순환도 같은 방식입니다.
@Module({
imports: [forwardRef(() => UserModule)],
})
export class AuthModule {}ModuleRef로 지연 조회하는 방법도 있습니다. 생성자에서 주입받는 대신 필요할 때 컨테이너에서 꺼내면 순환 자체가 사라집니다.
@Injectable()
export class UserService {
constructor(private moduleRef: ModuleRef) {}
async revoke(id: number) {
const authService = this.moduleRef.get(AuthService, { strict: false });
await authService.revokeSessions(id);
}
}- 같은 디렉터리 안에서는 배럴을 거치지 않습니다.
import { AuthService } from "./auth.service"; // 권장
import { AuthService } from "./"; // 순환을 만들 수 있음forwardRef는 해결이 아니라 유예입니다. 순환이 남아 있는 한 초기화 순서에 민감한 코드는 계속 위험합니다. 생성자에서 상대 서비스의 메서드를 바로 호출하는 코드는 특히 그렇습니다.
댓글0
댓글을 남기려면 로그인이 필요해요. 로그인
아직 댓글이 없어요. 첫 의견을 편하게 남겨 보세요.