提供程序 Providers

导读

提供者是 Nest 中的一个基本概念。许多基本的 Nest 类都可以被视为提供者 - 服务、存储库、工厂、助手等等。提供者的主要思想是它可以作为依赖项注入;这意味着对象可以彼此创建各种关系,并且连接这些对象的功能可以很大程度上委托给 Nest 运行时系统。

img

在上一章中,我们构建了一个简单的 CatsController。控制器应该处理 HTTP 请求并将更复杂的任务委托给提供者。提供者是普通的 JavaScript 类,在 模块 中声明为 提供者

提示

由于 Nest 能够以更面向对象的方式设计和组织依赖项,我们强烈建议遵循 SOLID 原则。

服务

让我们从创建一个简单的CatsService开始。此服务将负责数据存储和检索,并设计为由CatsController使用,因此它非常适合定义为提供程序。

ts
ts
ts
// cats.service
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 是一个可由 Nest IoC 容器管理的类。顺便说一句,此示例还使用了一个 Cat 接口,它可能看起来像这样:

interfaces/cat.interface
ts
export interface Cat {
  name: string
  age: number
  breed: string
}

现在我们有一个用于检索猫的服务类,让我们在CatsController中使用它:

ts
ts
ts
// cats.controller
import { Body, Controller, Get, Post } 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 是围绕通常称为 依赖注入 的强大设计模式构建的。我们建议您阅读官方 Angular 文档中有关此概念的精彩文章。

在 Nest 中,得益于 TypeScript 功能,管理依赖项非常容易,因为它们仅按类型解析。在下面的示例中,Nest 将通过创建并返回 CatsService 的实例来解析 catsService(或者,在单例的正常情况下,如果已在其他地方请求,则返回现有实例)。此依赖项已解析并传递给控制器​​的构造函数(或分配给指示的属性):

ts
constructor(private catsService: CatsService) {}

范围

提供程序通常具有与应用程序生命周期同步的生命周期(范围)。当应用程序启动时,必须解决每个依赖项,因此必须实例化每个提供程序。同样,当应用程序关闭时,每个提供程序都将被销毁。但是,也有方法可以使您的提供程序生命周期成为请求范围。您可以在此处 阅读有关这些技术的更多信息。

自定义提供程序

Nest 有一个内置的控制反转(IoC)容器,可以解决提供程序之间的关系。此功能是上述依赖注入功能的基础,但实际上比我们迄今为止描述的功能强大得多。有几种定义提供程序的方法:您可以使用普通值、类以及异步或同步工厂。此处 提供了更多示例。

可选提供程序

有时,您可能具有不一定必须解决的依赖项。例如,您的类可能依赖于配置对象,但如果未传递任何配置对象,则应使用默认值。在这种情况下,依赖项变为可选的,因为缺少配置提供程序不会导致错误。

要指示提供程序是可选的,请在构造函数的签名中使用@Optional()装饰器。

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

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

请注意,在上面的示例中,我们使用了自定义提供程序,这就是我们包含 HTTP_OPTIONS 自定义 token 的原因。前面的示例展示了基于构造函数的注入,通过构造函数中的类指示依赖关系。阅读有关自定义提供程序及其相关令牌的更多信息 此处

基于属性的注入

到目前为止,我们使用的技术称为基于构造函数的注入,因为提供程序是通过构造函数方法注入的。在某些非常特殊的情况下,基于属性的注入 可能会很有用。例如,如果您的顶级类依赖于一个或多个提供程序,则通过从构造函数调用子类中的 super() 将它们一直向上传递可能会非常繁琐。为了避免这种情况,您可以在属性级别使用 @Inject() 装饰器。

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

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

如果您的类不扩展其他类,则应始终优先使用基于构造函数的注入。构造函数明确概述了所需的依赖项,并且比使用 @Inject 注释的类属性提供了更好的可见性。

提供程序注册

现在我们已经定义了提供程序(CatsService),并且我们有该服务的消费者(CatsController),我们需要向 Nest 注册该服务,以便它可以执行注入。我们通过编辑模块文件(app.module.ts)并将服务添加到 @Module() 装饰器的 providers 数组来实现此目的。

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类的依赖项。

我们的目录结构现在应该是这样的:

bash
src
 ├─ cats
 ├─ dto
   └─ create-cat.dto.ts
 ├─ interfaces
   └─ cat.interface.ts
 ├─ cats.controller.ts
 └─ cats.service.ts
 ├─ app.module.ts
 └─ main.ts

手动实例化

到目前为止,我们已经讨论了 Nest 如何自动处理解析依赖项的大部分细节。在某些情况下,您可能需要跳出内置的依赖注入系统并手动检索或实例化提供程序。我们在下面简要讨论两个这样的主题。

要获取现有实例或动态实例化提供程序,您可以使用 模块参考

要在 bootstrap() 函数中获取提供程序(例如,对于没有控制器的独立应用程序,或在引导期间使用配置服务),请参阅 独立应用程序