动态模块进阶:异步Provider
本节是 NestJS 自定义动态模块中最核心的内容。在之前完成了同步的 forRoot 逻辑之后,接下来要实现 forRootAsync 的异步配置逻辑。理解这一部分需要对照 TypeORM 和 Mongoose 官方模块的源码来学习。
异步 Provider 的设计思路
NestJS 的异步 Provider 机制允许模块在初始化时通过以下方式获取配置:
- useFactory -- 使用工厂函数动态生成配置,可配合
inject注入 DI 容器中的其他服务实例 - useClass -- 提供一个类,该类实现特定的工厂接口,通过调用其方法获取配置
- useExisting -- 复用 DI 容器中已有的服务实例作为配置来源
官方模块源码分析
以 @nestjs/mongoose 和 @nestjs/typeorm 为例,它们的 Core Module 中都有两个关键方法:
createAsyncProviders-- 负责创建异步 Provider 的整体集合createAsyncOptionsProvider-- 负责创建配置选项的 Provider
// createAsyncProviders 内部逻辑示意
static createAsyncProviders(options): Provider[] {
if (options.useFactory || options.useExisting) {
// 当用户使用 useFactory 或 useExisting 时
return [this.createAsyncOptionsProvider(options)];
}
// 当用户使用 useClass 时
const useClass = options.useClass as PrismaOptionsFactory;
return [
this.createAsyncOptionsProvider(options),
{
provide: useClass,
useClass: useClass,
},
];
}
typescript
inject 与 useFactory 的协作关系
这是理解异步 Provider 的关键:
// useFactory + inject 是 NestJS DI 系统的固定写法
{
provide: PRISMA_MODULE_OPTIONS,
useFactory: (options: PrismaModuleOptions) => {
// 在这里使用注入的配置
return prismaClient;
},
inject: [PRISMA_MODULE_OPTIONS], // 声明需要注入的依赖
}
typescript
inject 数组相当于参数声明,useFactory 函数的参数按顺序对应 inject 中声明的依赖实例。NestJS 的 DI 系统负责在运行时将这些实例注入到工厂函数中。
PrismaModule 的 AsyncOptions 接口定义
为实现 forRootAsync,需要定义以下 TypeScript 接口:
// 异步配置选项接口
export interface PrismaModuleAsyncOptions
extends Pick<ModuleMetadata, 'imports'> {
name?: string;
useExisting?: Type<PrismaOptionsFactory>;
useClass?: Type<PrismaOptionsFactory>;
useFactory?: (
...args: any[]
) => Promise<PrismaModuleFactoryOptions> | PrismaModuleFactoryOptions;
inject?: any[];
}
// 工厂接口 -- 供 useClass/useExisting 使用
export interface PrismaOptionsFactory {
createPrismaModuleOptions(): Promise<PrismaModuleOptions> | PrismaModuleOptions;
}
// 工厂函数返回的选项类型(不含 name,避免冲突)
export type PrismaModuleFactoryOptions = Omit<PrismaModuleOptions, 'name'>;
typescript
为什么 useFactory 返回的类型要 Omit name? 因为外层的
PrismaModuleAsyncOptions已经定义了name字段,如果useFactory返回的对象也包含name,会造成属性冲突和外层配置被覆盖。
createAsyncOptionsProvider 实现
该方法的核心职责是根据用户传入的配置方式,创建对应的 Provider:
private static createAsyncOptionsProvider(
options: PrismaModuleAsyncOptions
): Provider {
if (options.useFactory) {
// 场景一:用户使用 useFactory
return {
provide: PRISMA_MODULE_OPTIONS,
useFactory: options.useFactory,
inject: options.inject || [],
};
}
// 场景二:用户使用 useExisting 或 useClass
const inject = [options.useExisting || options.useClass] as Type<PrismaOptionsFactory>[];
return {
provide: PRISMA_MODULE_OPTIONS,
useFactory: async (optionsFactory: PrismaOptionsFactory) => {
return optionsFactory.createPrismaModuleOptions();
},
inject,
};
}
typescript
createAsyncProviders 的完整逻辑
private static createAsyncProviders(
options: PrismaModuleAsyncOptions
): Provider[] {
// 如果有 useExisting 或 useFactory,直接返回 options provider
if (options.useExisting || options.useFactory) {
return [this.createAsyncOptionsProvider(options)];
}
// 如果有 useClass,额外注册 useClass 本身
const useClass = options.useClass as Type<PrismaOptionsFactory>;
return [
this.createAsyncOptionsProvider(options),
{ provide: useClass, useClass },
];
}
typescript
forRootAsync 方法的骨架
static forRootAsync(options: PrismaModuleAsyncOptions): DynamicModule {
const providers: Provider[] = [
// PrismaClient Provider -- 注入 PRISMA_MODULE_OPTIONS
{
provide: providerName,
useFactory: async (prismaModuleOptions: PrismaModuleOptions) => {
// 根据配置创建 PrismaClient 实例
// 异步场景下不需要手动 connect
// Prisma 在首次查询时会自动建立连接
},
inject: [PRISMA_MODULE_OPTIONS],
},
...this.createAsyncProviders(options),
];
return {
module: PrismaCoreModule,
providers,
exports: providers,
};
}
typescript
数据流总览
用户调用 forRootAsync({ useClass: PrismaService })
|
+-> createAsyncProviders(options)
| |
| +-> createAsyncOptionsProvider(options)
| |
| +-> 提供 PRISMA_MODULE_OPTIONS 常量
|
+-> PrismaClient Provider
|
+-> inject: [PRISMA_MODULE_OPTIONS]
+-> useFactory 中使用注入的配置创建 Client
text
无论用户选择 useFactory、useExisting 还是 useClass,最终都会产生一个 PRISMA_MODULE_OPTIONS 常量,供 PrismaClient Provider 注入使用。这种设计将配置获取方式与 Client 创建逻辑解耦,实现了高度灵活的异步初始化能力。
要点总结
inject是参数声明,useFactory的参数按序对应inject中的依赖实例PRISMA_MODULE_OPTIONS常量是连接createAsyncProviders和PrismaClient Provider的桥梁useFactory返回类型需要 Omitname,避免与外层配置冲突useClass场景下需要同时注册 Provider 和类本身- 异步场景中 Prisma 不需要手动调用
connect(),首次查询时自动建立连接
↑