Провайдеры — фундаментальная концепция Nest. Многие из основных классов Nest могуть рассматриваться, как провайдеры – сервисы, репозитории, фабрики, хелперы, и так далее. Основная идея провайдера заключается в том, что он может быть внедрен как зависимость; это означает, чтобы объекты могут создавать различные связи друг с дргуом, a функция «подключения» экземпляров объектов может быть в значительной степени делегирована системе выполнения Nest.
В предыдущей главе мы создали простой CatsController. Контроллеры должны обрабатывать HTTP запросы и делегировать более сложные задачи провайдерам. Провайдеры — это простые классы JavaScript, объявленные как провайдеры в модуле.
Подсказка Поскольку Nest дает возможность проектировать и организовывать зависимости более объектно-ориентированным способом, мы настоятельно рекомендует следовать принципам SOLID.
Начнем с создания простого «CatsService». Эта служба будет отвечать за хранение и извлечение данных и предназначена для использования CatsController, поэтому она является хорошим кандидатом на роль provider.
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; } }
Чтобы создать службу с помощью интерфейса командной строки, просто выполните команду$ nest g service cats.
Наш CatsService — это базовый класс с одним свойством и двумя методами. Единственная новая функция заключается в том, что он использует декоратор @Injectable(). Декоратор @Injectable() прикрепляет метаданные, которые объявляют, что CatsService — это класс, которым может управлять Nest IoC контейнер. Кстати, в этом примере также используется интерфейс «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 немедленно в одном и том же месте.
Nest построен на основе строгого шаблона проектирования, широко известного как Dependency injection. Мы рекомендуем прочитать отличную статью об этой концепции в официальной документации Angular.
В Nest, благодаря возможностям TypeScript, очень легко управлять зависимостями, поскольку они разрешаются только по типу. В приведенном ниже примере Nest разрешит catsService, создав и вернув экземпляр CatsService (или, в обычном случае синглтона, вернув существующий экземпляр, если он уже был запрошен в другом месте). Эта зависимость разрешается и передается конструктору вашего контроллера (или назначается указанному свойству):
constructor(private catsService: CatsService) {}
Провайдеры обычно имеют время жизни («область действия»), синхронизированное с жизненным циклом приложения. Когда приложение загружается, каждая зависимость должна быть разрешена, и, следовательно, должен быть создан экземпляр каждого провайдера. Точно так же, когда приложение закрывается, каждый провайдер будет уничтожен. Однако есть способы сделать время жизни вашего провайдера зависимым от запроса.
Nest имеет встроенный контейнер инверсии управления («IoC»), который разрешает отношения между providers. Эта функция лежит в основе описанной выше функции внедрения зависимостей, но на самом деле она намного мощнее, чем то, что мы описали до сих пор. Существует несколько способов определить provider-а: вы можете использовать простые значения, классы и либо асинхронные, либо синхронные фабрики.
Иногда у вас могут быть зависимости, которые не обязательно должны быть разрешены. Например, ваш класс может зависеть от объекта конфигурации, но если ни один из них не передан, следует использовать значения по умолчанию. В таком случае зависимость становится необязательной, так как отсутствие провайдера конфигурации не приведет к ошибкам.
Чтобы указать, что провайдер является необязательным, используйте декоратор @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; }
Warning Если ваш класс не расширяет другого провайдера, вы всегда должны отдавать предпочтение внедрению на основе конструктора.
еперь, когда мы определили поставщика («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 автоматически обрабатывает большинство деталей разрешения зависимостей. В определенных обстоятельствах вам может потребоваться выйти за пределы встроенной системы внедрения зависимостей и вручную получить или создать экземпляры поставщиков. Ниже мы кратко обсудим две такие темы.
Чтобы получить существующие экземпляры или динамически создавать экземпляры провайдеров, вы можете использовать Справочник по модулю.
Чтобы получить провайдеров в рамках функции bootstrap() (например, для автономных приложений без контроллеров или для использования службы настройки во время начальной загрузки), см. Автономные приложения .