NestJS提供者

Providers是Nest中的一个基本概念。许多Nest类可以是Provider,Service、Repository、Factory、Helper等。Providers是带有@Injectable装饰器的类,它可以注入依赖项,在对象之间创建各种关系。

Components

Controller和Provider的关系:Controller负责处理HTTP请求,Provider负责处理更复杂的业务,如数据库操作。建议按照[Solid]原则https://en.wikipedia.org/wiki/SOLID构建项目

依赖注入

创建service的命令是 nest g s 服务名

./interfaces/cat.interface

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

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;
  }
}

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 readonly catsService: CatsService) {}

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

  @Get()
  async findAll(): Promise {
    return this.catsService.findAll();
  }
}

上面的程序把提供者(CatsService)注入到控制器(CatsController)中

app.module.ts

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

在app.module.ts 中注册 controllers 和 providers。在实际开发中,更多的是在个模块的module.ts文件中注册,然后在 app.module.ts 中将它们 import 进来

依赖注入是一种控制反转(IoC)技术,即将依赖的实例化任务委派给IoC容器(这里为NestJS运行时系统)完成。这个过程有三个关键步骤:

  1. 在 cats.service.ts 中用 @Injectable() 装饰器将 CatsService声明为由Nest IoC 容器管理的类
  2. 在 cats.controller.ts 中,把 CatsService 这个token 对应实例注入到 CatsController
  3. 在 app.module 中 注册 Controllers 和 Providers

当Nest IoC 容器实例化时 CatsController 时,它首先查找所有依赖项,当找到 CatsService 依赖项时,对CatsService的token进行查找,如果在缓存中找到则返回 CatsService 实例,没有则创建 CatsService 实例并返回

标准提供者

providers: [CatsService] 是注册提供者的简写方法,其完整版为

providers: [
  {
    provide: CatsService,
    useClass: CatsService,
  },
];
自定义提供者(Custom providers)

NestJS 提供的标准提供者满足绝大多数使用场景,但以下情况可能需要自定义提供者:

  1. 创建自定义实例而不是Nest创建的实例
  2. 在另一段依赖关系中重用该类
  3. 重写该类用于测试
值提供者(useValue)

useValue用于注入常量值,经常用于mock数据进行测试

import { CatsService } from './cats.service';

const mockCatsService = {
  /* mock implementation
  ...
  */
};

@Module({
  imports: [CatsModule],
  providers: [
    {
      provide: CatsService,
      useValue: mockCatsService,
    },
  ],
})
export class AppModule {}

CatsController无需任何改变,但CatsService这个token会被解析为mockCatsService,注意,mockCatsService 和 CatsService有完全相同的接口

以上讨论的都是用类名作为提供者的token,还可以使用字符串作为token

import { connection } from './connection';

@Module({
  providers: [
    {
      provide: 'CONNECTION',
      useValue: connection,
    },
  ],
})
export class AppModule {}

这里直接把 CONNECTION 作为token赋值给provide,在实际情况通常定义一个 constants.ts 文件存放这些常量

注入以字符串为token的提供者方式有所不同,使用 @Inject() 装饰器,它只接受一个参数,即token的值

@Injectable()
export class CatsRepository {
  constructor(@Inject('CONNECTION') connection: Connection) {}
}
类提供者(useClass) 重点

useClass允许你动态决定令牌应该解释的类

const configServiceProvider = {
  provide: ConfigService,
  useClass:
    process.env.NODE_ENV === 'development'
      ? DevelopmentConfigService
      : ProductionConfigService,
};

@Module({
  providers: [configServiceProvider],
})
export class AppModule {}

ConfigService可以是抽象类或默认类,DevelopmentConfigService 和 ProductionConfigService 必须是实实在在的类,上面代码将 ConfigService 类名作为令牌,对于任何依赖ConfigService 的类,Nest 都会注入 DevelopmentConfigService 或者 ProductionConfigService 的实例,注意,ConfigService需要用 @Injectable() 装饰

可选provider

有时某些依赖项是可选的,当没有传递时(在module中传递),则使用默认值。用@Optional()装饰可选provider

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

@Injectable()
export class HttpService {
  constructor(
    @Optional() @Inject('HTTP_OPTIONS') private readonly httpClient: T
  ) {}
}
基于属性的注入

如果一个顶级类依赖多个provider,基于属性注入会是较好选择

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

@Injectable()
export class HttpService {
  @Inject('HTTP_OPTIONS')
  private readonly httpClient: T;
}
注册provider
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 {}
循环依赖

当A类需要B类时,而B类也需要A类时,就会产生循环依赖,Nest允许在提供者(provider)和模块(module)之间创建循环依赖关系,但建议尽量避免,以下方法可以解决这个问题

正向引用

正向引用允许Nest引用目前尚未被定义的引用,当CatsService和CommonService相互依赖时,关系的双方都需要使用@Inject()和forwordRef(),否则Nest不会实例化它们,因为所有的元素都不可用。

cats.service.ts

@Injectable()
export class CatsService {
  constructor(
    @Inject(forwardRef(() => CommonService))
    private readonly commonService: CommonService,
  ) {}
}

在CommonService需要做同样的事

@Injectable()
export class CommonService {
  constructor(
    @Inject(forwardRef(() => CatsService))
    private readonly catsService: CatsService,
  ) {}
}
模块引用

为了在模块(module)之间创建循环依赖,在两个模块都必须使用forwardRef()

@Module({
  imports: [forwardRef(() => CatsModule)],
})
export class CommonModule {}

Nest提供ModuleRef类可以将组件快速注入到其他组件,它有一个get方法来获得provider

cats.service.ts

@Injectable()
export class CatsService implements OnModuleInit {
  private service: Service;
  constructor(private readonly moduleRef: ModuleRef) {}

  onModuleInit() {
    this.service = this.moduleRef.get(Service);
  }
}

如果使用非严格模式,整个应用可以应用这个provider

this.moduleRef.get(Service, { strict: false });

你可能感兴趣的:(NestJS提供者)