Провайдеры - это фундаментальная концепция в Nest. Многие из основных классов Nest могут рассматриваться как провайдеры - сервисы, репозитории, фабрики, хелперы и так далее. Основная идея провайдера заключается в том, что он может быть внедрен как зависимость; это означает, что объекты могут создавать различные отношения друг с другом, а функция "подключения" экземпляров объектов может быть в значительной степени делегирована системе выполнения Nest.

Контроллеры должны обрабатывать HTTP-запросы и делегировать более сложные задачи провайдерам. Провайдеры - это обычные классы JavaScript, которые объявляются в модуле как providers.

Поскольку Nest дает возможность проектировать и организовывать зависимости более OO-способом, мы настоятельно рекомендуем следовать принципам SOLID.

Сервисы

Давайте начнем с создания простого сервиса CatsService. Этот сервис будет отвечать за хранение и получение данных и предназначен для использования контроллером CatsController, поэтому он является хорошим кандидатом на определение в качестве провайдера.

cats.service.ts

import { Injectable } from '@nestjs/common';
import { Cat } from './interfaces/cat.interface';

@Injectable()
export class CatsService {
  private readonly cats: Cat[] = [];

  create(cat: Cat) {
    this.cats.push(cat);
  }

  findAll(): Cat[] {
    return this.cats;
  }
}

Чтобы создать сервис с помощью CLI, просто выполните команду $ nest g service cats

Наш CatsService - это базовый класс с одним свойством и двумя методами. Единственной новой особенностью является то, что он использует декоратор @Injectable(). Декоратор @Injectable() объявляет, что CatsService - это класс, которым может управлять IoC-контейнер Nest. Кстати, в этом примере также используется интерфейс Cat, который, вероятно, выглядит примерно так:

interfaces/cat.interface.ts

export interface Cat {
  name: string;
  age: number;
  breed: string;
}

Теперь, когда у нас есть класс сервиса для получения кошек, давайте используем его внутри CatsController:

cats.controller.ts

import { Controller, Get, Post, Body } from '@nestjs/common';
import { CreateCatDto } from './dto/create-cat.dto';
import { CatsService } from './cats.service';
import { Cat } from './interfaces/cat.interface';

@Controller('cats')
export class CatsController {
  constructor(private catsService: CatsService) {}

  @Post()
  async create(@Body() createCatDto: CreateCatDto) {
    this.catsService.create(createCatDto);
  }

  @Get()
  async findAll(): Promise<Cat[]> {
    return this.catsService.findAll();
  }
}

CatsService инжектируется через конструктор класса. Обратите внимание на использование синтаксиса private. Это сокращение позволяет нам и объявить, и инициализировать catsService сразу в одном и том же месте.

Внедрение зависимостей (Dependency injection)

Nest построен на основе классного паттерна проектирования, известного как внедрение зависимостей. В Nest, благодаря возможностям TypeScript, очень легко управлять зависимостями, поскольку они разрешаются просто по типу. В приведенном ниже примере Nest разрешит зависимость catsService, создав и вернув экземпляр CatsService (или, в обычном случае синглтона, вернув существующий экземпляр, если он уже был запрошен в другом месте). Эта зависимость разрешается и передается в конструктор вашего контроллера (или присваивается указанному свойству):

constructor(private catsService: CatsService) {}

Scopes

Провайдеры обычно имеют время жизни ("scope"), синхронизированное с жизненным циклом приложения. Когда приложение загружается, каждая зависимость должна быть разрешена, и поэтому каждый провайдер должен быть инициализирован. Аналогично, при завершении работы приложения каждый провайдер будет уничтожен. Однако существуют способы сделать так, чтобы время жизни провайдера также зависело от запросов.

Пользовательские провайдеры

Nest имеет встроенный контейнер инверсии управления ("IoC"), который разрешает отношения между провайдерами. Эта функция лежит в основе функции dependency injection, описанной выше, но на самом деле она гораздо мощнее, чем то, что мы описали до сих пор. Существует несколько способов определения провайдера: вы можете использовать простые значения, классы, асинхронные или синхронные фабрики.

Необязательные провайдеры

Иногда у вас могут быть зависимости, которые не обязательно должны быть разрешены. Например, ваш класс может зависеть от объекта конфигурации, но если он не передан, то следует использовать значения по умолчанию. В таком случае зависимость становится необязательной, поскольку отсутствие провайдера конфигурации не приведет к ошибкам.

Чтобы указать, что провайдер является необязательным, используйте декоратор @Optional() в аргументах конструктора.

import { Injectable, Optional, Inject } from '@nestjs/common';

@Injectable()
export class HttpService<T> {
  constructor(@Optional() @Inject('HTTP_OPTIONS') private httpClient: T) {}
}

Обратите внимание, что в приведенном примере мы используем пользовательский провайдер, и именно поэтому мы включаем пользовательский маркер HTTP_OPTIONS. Предыдущие примеры показывали инъекцию на основе конструктора, указывающую на зависимость через класс в конструкторе.

Инъекция на основе свойств

Техника, которую мы использовали до сих пор, называется инъекцией на основе конструктора, поскольку провайдеры инжектируются через метод конструктора. В некоторых специфических случаях может оказаться полезной инъекция на основе свойств. Например, если ваш класс верхнего уровня зависит от одного или нескольких провайдеров, передавать их по всему пути наверх, вызывая super() в подклассах из конструктора, может быть очень утомительно. Чтобы избежать этого, вы можете использовать декоратор @Inject() на уровне свойств.

import { Injectable, Inject } from '@nestjs/common';

@Injectable()
export class HttpService<T> {
  @Inject('HTTP_OPTIONS')
  private readonly httpClient: T;
}

Регистрация провайдера

Теперь, когда мы определили провайдера (CatsService) и у нас есть контроллер (CatsController), нам нужно зарегистрировать сервис в Nest, чтобы он мог выполнять инъекции. Для этого нужно отредактировать файл нашего модуля (app.module.ts) и добавить сервис в массив providers декоратора @Module().

app.module.ts

import { Module } from '@nestjs/common';
import { CatsController } from './cats/cats.controller';
import { CatsService } from './cats/cats.service';

@Module({
  controllers: [CatsController],
  providers: [CatsService],
})
export class AppModule {}

Теперь Nest сможет разрешить зависимости класса CatsController.

Ручное создание экземпляров

До сих пор мы обсуждали, как Nest автоматически обрабатывает большинство деталей разрешения зависимостей. В некоторых случаях вам может потребоваться выйти за рамки встроенной системы Dependency Injection и вручную получить или создать экземпляр провайдера. Ниже мы кратко рассмотрим две такие темы.

Чтобы получить существующие экземпляры или создать экземпляр провайдера динамически, вы можете использовать ссылку на модуль.