在构建复杂的后端应用中,如何有效地管理和彼此协作的各个部分,以及如何共享和复用功能已成为开发者们重要关注的问题。覆盖这些需求的一种技术就是 NestJS 中的 Providers。这次,让我们一起深入探讨这个关键概念,解密 Providers 的奥秘,了解它们是如何提供和分配服务的。
Provider 是 NestJS 中的一种核心构建块,它提供了封装与分享服务逻辑的方式。实际上,你可以将 Provider 看作是一种可以注入到任何地方的对象或实体。这些对象可以是类、工厂、值甚至更复杂的结构。不管它返回什么,它的核心意图是提供一种实现不同任务的方式,例如数据访问、辅助函数,所有这些都尽可能地可复用和可测试。
在 NestJS 中有五种主要的 Provider:
以下内容,我们将详细深入探讨这五种类型的 Provider,并给出使用案例。
值提供者是最直接的一种类型,它直接返回一个常量或者预定义的值。且更常见的用例是,将应用的配置注入到需要读取配置的服务中。
将配置对象作为值 Provider 的例子:
// 在模块定义中我们创建一个值 Provider
@Module({
providers: [
{
provide: 'DATABASE_CONFIG',
useValue: {
host: 'localhost',
port: 5432,
user: 'dbuser',
password: 'dbpassword',
database: 'myDatabase'
}
}
]
})
// 在服务中,我们可以将这个值注入进来:
@Injectable()
export class DatabaseService {
constructor(@Inject('DATABASE_CONFIG') private dbConfig: any) {}
getConnectionDetails() {
// 输出数据库配置信息
console.log(this.dbConfig);
}
}
类提供者是 Provider 中常用的一种,它可以让我们通过依赖注入的方式来获取类的实例。这对复用和测试非常有价值,因为我们可以在测试时使用模拟的实例来替换真正的服务。
下面讲述了如何使用和注入类提供者的例子:
// 我们首先在模块定义中声明一个类提供者
@Module({
providers: [ConfigService],
})
// 接着在服务中,我们可以注入这个提供者
@Injectable()
export class UserService {
constructor(private configService: ConfigService) {}
getEnvironment() {
// 输出当前应用环境
console.log(this.configService.get('ENV'))
}
}
工厂提供者是最灵活的 Provider 形式。工厂函数可以返回任意值,并且可以用来执行复杂的同步或异步操作。同样的,这让我们可以根据运行时逻辑返回不同的结果或者根据不同的运行环境提供不同的实现。
下面是工厂提供者的一个例子:
// 在模块定义中,我们创建一个工厂提供者
@Module({
providers: [
{
provide: 'ENV_CONFIG',
useFactory: () => {
// 例如,我们可以在此基于一些环境变量返回不同的配置:
return process.env.NODE_ENV === 'development'
? developmentConfig
: productionConfig;
},
},
],
})
// 届时我们可以在服务中注入这个工厂提供者
@Injectable()
export class UserService {
constructor(@Inject('ENV_CONFIG') private envConfig: any) {}
getConfig() {
// 输出当前的环境配置
console.log(this.envConfig);
}
}
异步工厂提供者与工厂提供者在原则上是相似的,但它们返回一个 Promise 或 Observable。当 Promise 解析或者 Observable 发射出结果时,这个结果就会在应用中作为注入的值。这项特性对于需要进行异步操作来提供值的需求场景非常有用,比如数据库连接,远程配置等。
以下是异步工厂提供者的一个应用场景:
//在模块定义中我们先定义一个异步工厂提供者
@Module({
providers: [
{
provide: 'DATABASE_CONNECTION',
useFactory: async () => {
const options = await getDbOptions();
return createConnection(options);
},
},
],
})
// 在服务中,我们可以注入这个数据库连接
@Injectable()
export class UserService {
constructor(
@Inject('DATABASE_CONNECTION') private dbConnection: Connection
) {}
findUser(id: number) {
return this.dbConnection.getRepository(User).findOne(id);
}
}
在这个示例中,DATABASE_CONNECTION
是一个异步工厂提供者,这个提供者会异步地创建数据库连接。
别名提供者允许我们为提供者赋予别名,这样我们可以在不同上下文中引用并使用同样的值。这在某些需要多次引用同一个提供者或者希望使用更具语义化名称的场景中很有用。
下面是一个别名提供者的使用示例:
// 在模块定义中,我们创建 ConfigService 的别名
@Module({
providers: [
ConfigService,
{
provide: 'AppConfig',
useExisting: ConfigService,
},
],
})
// 在服务中,我们可以使用别名来注入这个提供者
@Injectable()
export class UserService {
constructor(@Inject('AppConfig') private configService: ConfigService) {}
getEnvironment() {
// 输出当前应用环境
console.log(this.configService.get('ENV'));
}
}
在这个示例中,AppConfig
是 ConfigService
的别名,所以当我们请求 AppConfig
的时候,我们实际上获取到的是 ConfigService
的实例。
本文介绍了 NestJS 中绝大多数的 provider 类型及其使用方式。希望这篇文章能帮助你理解不同的 provider 及其适用的场景。但我们这次的探索只是冰山一角,NestJS 还有很多高级特性等待你去挖掘。