Providers是Nest中的一个基本概念。许多Nest类可以是Provider,Service、Repository、Factory、Helper等。Providers是带有@Injectable装饰器的类,它可以注入依赖项,在对象之间创建各种关系。
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运行时系统)完成。这个过程有三个关键步骤:
- 在 cats.service.ts 中用 @Injectable() 装饰器将 CatsService声明为由Nest IoC 容器管理的类
- 在 cats.controller.ts 中,把 CatsService 这个token 对应实例注入到 CatsController
- 在 app.module 中 注册 Controllers 和 Providers
当Nest IoC 容器实例化时 CatsController 时,它首先查找所有依赖项,当找到 CatsService 依赖项时,对CatsService的token进行查找,如果在缓存中找到则返回 CatsService 实例,没有则创建 CatsService 实例并返回
标准提供者
providers: [CatsService] 是注册提供者的简写方法,其完整版为
providers: [
{
provide: CatsService,
useClass: CatsService,
},
];
自定义提供者(Custom providers)
NestJS 提供的标准提供者满足绝大多数使用场景,但以下情况可能需要自定义提供者:
- 创建自定义实例而不是Nest创建的实例
- 在另一段依赖关系中重用该类
- 重写该类用于测试
值提供者(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 });