Запропонувати правкуПокращити цю статтюДопрацюйте відповідь до «Циклічна залежність модулів». Ваші зміни проходять модерацію перед публікацією.Потрібне підтвердженняКонтентЩо ви змінюєте🇺🇸EN🇺🇦UAПереглядЗаголовок (UA)Коротка відповідь (UA)Циклічна залежність виникає, коли два (чи більше) модулі або провайдери залежать одне від одного напряму чи через ланцюжок; Nest не може побудувати граф залежностей і кидає помилку `A circular dependency has been detected between modules`, застосунок не запускається. **Ключове:** вирішується через `forwardRef()` - для модулів у `imports: [forwardRef(() => OtherModule)]`, для сервісів через `@Inject(forwardRef(() => OtherService))`; краще ж переглянути архітектуру й винести спільну логіку в окремий `SharedModule`.Показується над повною відповіддю для швидкого нагадування.Відповідь (UA)Зображення## Що таке циклічна залежність **Циклічна залежність (circular dependency)** виникає, коли **два (чи більше) модулі чи провайдери залежать одне від одного напряму чи через ланцюжок**. Наприклад: ```javascript AuthModule → UsersModule → AuthModule ``` чи ```javascript AuthService → UsersService → AuthService ``` ## Приклад проблеми з модулями ```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" |Для рев’юераПримітка для модератора (необов’язково)Бачить лише модератор. Прискорює рев’ю.