Циклічна залежність модулів
Що таке циклічна залежність
Циклічна залежність (circular dependency) виникає, коли два (чи більше) модулі чи провайдери залежать одне від одного напряму чи через ланцюжок.
Наприклад:
AuthModule → UsersModule → AuthModuleчи
AuthService → UsersService → AuthServiceПриклад проблеми з модулями
// auth.module.ts
@Module({
imports: [UsersModule], // AuthModule залежить від UsersModule
providers: [AuthService],
})
export class AuthModule {}// users.module.ts
@Module({
imports: [AuthModule], // UsersModule залежить від AuthModule
providers: [UsersService],
})
export class UsersModule {}При запуску:
Error: Nest cannot create the module instance.
A circular dependency has been detected between modules.NestJS не може побудувати граф залежностей, бо кожен модуль чекає, поки створиться інший.
Чому це відбувається
Nest будує дерево залежностей (Dependency Graph):
- Коли
AuthModuleімпортуєUsersModule, Nest спершу має створитиUsersModule. - Але
UsersModule, своєю чергою, імпортуєAuthModule, і процес зациклюється.
У результаті NestJS не знає, з чого почати - і кидає помилку про циклічну залежність.
Аналогічна проблема буває й у провайдерів
@Injectable()
export class AuthService {
constructor(private readonly usersService: UsersService) {}
}
@Injectable()
export class UsersService {
constructor(private readonly authService: AuthService) {}
}Тут NestJS теж "застрягає" при створенні екземплярів:
AuthServiceпотребуєUsersService,- але щоб створити
UsersService, потрібенAuthService.
Як вирішити проблему
NestJS надає спеціальний інструмент - forwardRef().
Рішення для модулів
Використай forwardRef() при імпорті залежного модуля:
// auth.module.ts
@Module({
imports: [forwardRef(() => UsersModule)], // ключова зміна
providers: [AuthService],
exports: [AuthService],
})
export class AuthModule {}// users.module.ts
@Module({
imports: [forwardRef(() => AuthModule)], // взаємна залежність дозволена
providers: [UsersService],
exports: [UsersService],
})
export class UsersModule {}Тепер Nest знає, що ці модулі посилаються одне на одного "ліниво", і може коректно вибудувати граф залежностей.
Рішення для сервісів
Теж за допомогою forwardRef():
@Injectable()
export class AuthService {
constructor(
@Inject(forwardRef(() => UsersService))
private readonly usersService: UsersService,
) {}
}
@Injectable()
export class UsersService {
constructor(
@Inject(forwardRef(() => AuthService))
private readonly authService: AuthService,
) {}
}forwardRef() каже DI-контейнеру:
"Створи залежність не зараз, а коли вона стане реально потрібна".
Як це працює всередині
- При звичайній залежності Nest створює екземпляри в момент ініціалізації.
- При
forwardRef()він реєструє ліниве посилання (lazy reference) - і підставляє залежність пізніше, коли всі провайдери будуть доступні.
Поради з проєктування (щоб уникнути циклів)
- Розділяй відповідальність: якщо два модулі залежать одне від одного - значить, логіку спроєктовано невдало.
→ Виділи спільну частину в третій модуль (наприклад,
SharedModuleчиCoreModule). - Експортуй лише потрібне. Не експортуй цілі модулі без потреби - лише конкретні сервіси.
- Уникай взаємних залежностей у сервісах. Іноді можна замінити пряме посилання на передачу подій, колбеків чи репозиторіїв.
Що буде, якщо не використовувати forwardRef()
NestJS:
-
не зможе розв'язати залежності;
-
кине помилку:
javascriptNest can't resolve dependencies of the AuthService (?). Please make sure that the argument UsersService at index [0] is available in the AuthModule context.
І застосунок просто не запуститься.
Підсумок
| Пункт | Опис |
|---|---|
| Що це | Ситуація, коли два модулі чи сервіси залежать одне від одного |
| Причина | Nest не може визначити порядок створення екземплярів |
| Рішення | Використати forwardRef(() => TargetModule) чи @Inject(forwardRef(() => TargetService)) |
| Кращий спосіб | Переглянути архітектуру й винести спільну логіку в окремий модуль |
| Якщо не виправити | Помилка при запуску: "Circular dependency detected" |
Коротка відповідь
Для співбесідиКоротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.