Пошук уроків, статей та іншого контенту
Як NestJS сам створює й передає залежності — і три режими життєвого циклу провайдерів.
Без DI клас, якому потрібен сервіс, створював би його екземпляр самостійно всередині себе (new CatsService()) — це жорстко прив'язує клас до конкретної реалізації залежності, ускладнює підміну цієї залежності в тестах (наприклад, на фейкову версію без реальної бази даних) і ускладнює повторне використання одного екземпляра сервісу в кількох місцях.
// Без DI — контролер сам створює й жорстко прив'язаний до конкретного CatsService
class CatsController {
private catsService = new CatsService(); // жорстка залежність, важко підмінити в тестах
}
// З DI — контролер лише оголошує, що йому потрібен CatsService,
// а НАДАННЯ конкретного екземпляра — відповідальність NestJS
@Controller("cats")
class CatsController {
constructor(private readonly catsService: CatsService) {} // NestJS сам передасть екземпляр
}Dependency Injection у NestJS реалізований через Inversion of Control (IoC) контейнер — під час старту застосунку NestJS сканує всі модулі, будує граф залежностей (хто що потребує) і сам створює екземпляри провайдерів у правильному порядку, передаючи їх у конструктори класів, що їх запитують. Клас лише декларує залежність через тип параметра конструктора — сам процес створення й передачі повністю прихований від нього.
За замовчуванням NestJS створює провайдер як singleton — один екземпляр на весь застосунок, спільний для всіх запитів. Це ефективно (немає накладних витрат на повторне створення), але не завжди підходить — NestJS підтримує три режими:
DEFAULT (singleton) — один спільний екземпляр на весь застосунок; типовий і найпродуктивніший вибір для більшості сервісів без стану, специфічного для конкретного запиту.
REQUEST — новий екземпляр на кожен вхідний HTTP-запит; потрібен, коли сервіс має тримати дані, специфічні саме для поточного запиту (наприклад, поточного автентифікованого користувача).
TRANSIENT — новий екземпляр щоразу, коли провайдер запитують як залежність, навіть у межах одного запиту.
import { Injectable, Scope } from "@nestjs/common";
@Injectable({ scope: Scope.REQUEST })
export class RequestContextService {
currentUserId: string | null = null; // безпечно тримати дані конкретного запиту — новий екземпляр на кожен запит
}REQUEST- і TRANSIENT-провайдери мають реальну ціну продуктивності — новий екземпляр (і весь граф залежностей, що на нього посилається) створюється значно частіше, ніж для singleton. Використовуйте їх свідомо, лише коли справді потрібен стан, специфічний для запиту чи виклику, а не як типовий вибір.
Тримати дані, специфічні для конкретного запиту (наприклад, поточного користувача), у звичайному singleton-провайдері — оскільки екземпляр один на весь застосунок, дані одного запиту можуть «протекти» в обробку іншого паралельного запиту.
Використовувати REQUEST-scope «про всяк випадок» без реальної потреби — непотрібно жертвує продуктивністю там, де підійшов би звичайний singleton.
Створювати екземпляри сервісів вручну (new SomeService()) замість впровадження через конструктор — втрачає всі переваги DI (тестованість, керований NestJS життєвий цикл).
Dependency Injection у NestJS реалізується через IoC-контейнер: клас лише декларує потрібні залежності в конструкторі, а сам NestJS створює й передає їхні екземпляри, будуючи граф залежностей під час старту застосунку. Область видимості провайдера (DEFAULT/REQUEST/TRANSIENT) визначає, наскільки часто створюється новий екземпляр — singleton за замовчуванням найпродуктивніший, REQUEST/TRANSIENT потрібні лише для стану, специфічного для конкретного запиту чи виклику.