Nestjs 学习记录:(一)Providers

252 阅读4分钟

一、Provider Overview

Provider 是 Nestjs 中的一个基本概念,很多 Nest 类,包括 services, repositories, factories, helpers 等等都可以被视为 Provider 并作为依赖注入。这意味着多个对象彼此之间可以创建各种关系,且“连接”这些对象的功能可以委托给 Nest 运行时系统而无需开发者自己处理。

示例

以下是一个包含 数据存储检索 功能的 CatsService 类,它是为了提供给 CatsController 使用而被设计出来的,因此,很适合被定义为 Provider。

import { Injectable } from '@nestjs/common';
​
@Injectable()
export class CatsService {
  constructor() {
    this.cats = [];
  }
​
  create(cat) {
    this.cats.push(cat);
  }
​
  findAll() {
    return this.cats;
  }
}

为了让普通的类能够被 Nest 识别为 Provider,我们需要给这个类添加 @Injectable() 装饰器来绑定用于声明 CatsService 能够被 Nest 的 Ioc 容器管理的元数据。

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 {
  // 在 TypeScript 中,参数属性是一种特殊的语法,它允许你在一个地方定义并初始化一个类的成员。
  // 具体来说,你可以在构造函数的参数前面添加 public、private、protected 或 readonly 修饰符,TypeScript 就会自动为你创建并初始化一个同名的类成员
  // private 可以让我们同时完成 CatsService 的声明和初始化的工作
  constructor(private catsService: CatsService) {}
​
  @Post()
  async create(@Body() createCatDto: CreateCatDto) {
    this.catsService.create(createCatDto);
  }
​
  @Get()
  async findAll(): Promise<Cat[]> {
    return this.catsService.findAll();
  }
}

上述内容中,我们已经定义了一个 CatsService 和一个 CatsController,此时我们还需要在对应的模块中注册 CatsService 让 Nest 在运行时能够执行注入操作。

import { Module } from '@nestjs/common';
import { CatsController } from './cats.controller';
import { CatsService } from './cats.service';
​
@Module({
  controllers: [CatsController],
  providers: [CatsService],
})
export class CatsModule {}

至此,我们已经初步了解到一个 Nest 功能模块的基本目录结构:

src
-- cats
    -- dto
        -- create-cat.dto.ts
    -- interfaces
        -- cat.interface.ts
    -- cats.controller.ts
    -- cats.service.ts
-- app.module.ts
-- main.ts

二、Custom Providers

(一) DI fundamentals

依赖注入dependency injection是一种 IoC(控制反转)技术,你可以将依赖的实例化过程移交给 IoC 容器(Nest 的运行时系统),无须开发者在代码中手动执行这些操作。

先回顾下第一节的内容:

  1. @Injectable() 装饰器用于将 CatsService 声明为可以被 Nest IoC 容器管理的类

  2. 通过 constructor(private catsService: CatsService) 声明依赖注入

  3. 将 CatsService 令牌与 './cats.service' 文件中 CatsService 类建立联系

当 Nest IoC 容器实例化 CatsController 时,首先会查找它的每一个依赖。比如,当发现有 CatsService 依赖时,会根据上述的三个注册步骤进行查找并返回 CatsService 类进行实例化【默认使用单例模式】,创建实例后会将其缓存,如果其他模块引用时发现该实例在缓存中已经存在,则返回缓存池的实例,无需再次执行实例化操作。

此外,Nest 解析代码依赖的过程是传递性的,在上述示例中,如果 CatsService 自身也有依赖项,这些依赖也会按照上述过程进行处理,从而保证依赖关系图能够按照正确的顺序【自下而上】处理

(二) None-class-based Provider tokens

有时,我们可能会希望用更灵活的方式,比如 String | Symbol 作为依赖注入的 token

import { connection } from './connection';
​
@Module({
  providers: [
    /*
        providers: [CatsService]
        相当于
            {
                provide: CatsService,
                useClass: CatsService
            }
        
        useValue 语法可以用来注入常量、将外部库注入Nest或者使用模拟对象替换具体实现
    */
    {
      provide: 'CONNECTION',
      useValue: connection,
    },
  ],
})
export class AppModule {}
// 对于上述 None-class-based Provider,需要使用 @Inject(token) 来注入依赖
@Injectable()
export class CatsRepository {
  constructor(@Inject('CONNECTION') connection: Connection) {}
}

(三) Factory Providers: useFactory

useFactory支持动态创建 provider,接受使用者传入的参数,并将函数的返回结果作为最终注入的依赖

inject属性接收一个 provider 数组,并在实例化过程中将其中的元素作为参数传递给工厂函数

const connectionProvider = {
  provide: 'CONNECTION',
  useFactory: (optionsProvider: OptionsProvider, optionalProvider?: string) => {
    const options = optionsProvider.get();
    return new DatabaseConnection(options);
  },
  inject: [OptionsProvider, { token: 'SomeOptionalProvider', optional: true }],
  //       _____________/            __________________/
  //        This provider              The provider with this
  //        is mandatory.              token can resolve to `undefined`.
};
​
@Module({
  providers: [
    connectionProvider,
    OptionsProvider,
    // { provide: 'SomeOptionalProvider', useValue: 'anything' },
  ],
})
export class AppModule {}
​

(四) Alias Providers: useExisting

useExisting可以为存在的 provider 创建一个别名,让我们能使用两种方式访问到同一个 provider 实例

@Injectable()
class LoggerService {
  /* implementation details */
}
​
const loggerAliasProvider = {
  provide: 'AliasedLoggerService',
  useExisting: LoggerService,
};
​
@Module({
  providers: [LoggerService, loggerAliasProvider],
})
export class AppModule {}

(五) Asynchronous Providers

当我们希望程序在一个或多个异步任务执行完毕后启动,比如连接数据库等,可以在 useFactory 中使用 async/await,Nest 会等待任务执行完成后再实例化依赖该 provider 的类

{
  provide: 'ASYNC_CONNECTION',
  useFactory: async () => {
    const connection = await createConnection(options);
    return connection;
  }
}