Skip to main content

Циклічна залежність модулів

Що таке циклічна залежність

Циклічна залежність (circular dependency) виникає, коли два (чи більше) модулі чи провайдери залежать одне від одного напряму чи через ланцюжок.

Наприклад:

javascript
AuthModuleUsersModuleAuthModule

чи

javascript
AuthServiceUsersServiceAuthService

Приклад проблеми з модулями

javascript
// auth.module.ts @Module({ imports: [UsersModule], // AuthModule залежить від UsersModule providers: [AuthService], }) export class AuthModule {}
javascript
// users.module.ts @Module({ imports: [AuthModule], // UsersModule залежить від AuthModule providers: [UsersService], }) export class UsersModule {}

При запуску:

javascript
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 не знає, з чого почати - і кидає помилку про циклічну залежність.

Аналогічна проблема буває й у провайдерів

javascript
@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() при імпорті залежного модуля:

javascript
// auth.module.ts @Module({ imports: [forwardRef(() => UsersModule)], // ключова зміна providers: [AuthService], exports: [AuthService], }) export class AuthModule {}
javascript
// users.module.ts @Module({ imports: [forwardRef(() => AuthModule)], // взаємна залежність дозволена providers: [UsersService], exports: [UsersService], }) export class UsersModule {}

Тепер Nest знає, що ці модулі посилаються одне на одного "ліниво", і може коректно вибудувати граф залежностей.

Рішення для сервісів

Теж за допомогою forwardRef():

javascript
@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) - і підставляє залежність пізніше, коли всі провайдери будуть доступні.

Поради з проєктування (щоб уникнути циклів)

  1. Розділяй відповідальність: якщо два модулі залежать одне від одного - значить, логіку спроєктовано невдало. → Виділи спільну частину в третій модуль (наприклад, SharedModule чи CoreModule).
  2. Експортуй лише потрібне. Не експортуй цілі модулі без потреби - лише конкретні сервіси.
  3. Уникай взаємних залежностей у сервісах. Іноді можна замінити пряме посилання на передачу подій, колбеків чи репозиторіїв.

Що буде, якщо не використовувати forwardRef()

NestJS:

  • не зможе розв'язати залежності;

  • кине помилку:

    javascript
    Nest 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"

Коротка відповідь

Для співбесіди
Premium

Коротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.