4. NestJS 依赖注入(DI)深入:Provider、Module、自定义Provider、工厂模式、异步Provider
依赖注入是 NestJS 架构的基石。在微服务场景下,服务间的解耦、熔断器的动态切换、配置的异步加载,都依赖对 DI 系统的深度理解。本章将穿透 Provider 与 Module 的核心机制,掌握从标准注入到工厂模式、异步 Provider 的进阶用法。
4.1 Provider 的本质与分类
在 NestJS 中,几乎所有的东西都可以作为 Provider:服务、仓库、工厂、配置对象、甚至是值。Provider 的核心职责是“提供实例”,而 DI 容器负责将这些实例注入到需要它们的构造函数中。
| Provider 类型 | 语法糖 | 适用场景 |
|---|---|---|
| 标准类 Provider | @Injectable() + 类名 |
绝大多数业务服务 |
| 值 Provider | { provide: TOKEN, useValue: ... } |
常量、配置对象、第三方库实例 |
| 类 Provider | { provide: TOKEN, useClass: ... } |
策略模式、环境切换、Mock 测试 |
| 工厂 Provider | { provide: TOKEN, useFactory: ... } |
动态创建、依赖外部条件、异步初始化 |
| 别名 Provider | { provide: TOKEN, useExisting: ... } |
为已有 Provider 创建别名 |
@Inject() 装饰器显式注入。
4.2 Module 的封装与导出
Module 是组织 Provider 的逻辑单元。每个模块可以声明自己的 Provider,并通过 exports 数组决定哪些 Provider 对外可见。微服务架构中,合理的模块划分决定了系统的可维护性。
// 基础模块结构
@Module({
imports: [ConfigModule], // 导入其他模块
controllers: [OrderController], // 控制器
providers: [OrderService], // 注册 Provider
exports: [OrderService], // 导出供其他模块使用
})
export class OrderModule {}
- Provider 默认只在声明它的模块内可见。
- 必须通过
exports显式导出,其他模块才能注入。 - 全局模块使用
@Global()装饰器,但应谨慎使用,避免隐式依赖。 - 动态模块(
forRoot/forFeature)允许在导入时传递配置参数。
4.3 自定义 Provider:从 Token 到实例的完全控制
当标准类注入无法满足需求时,自定义 Provider 提供了对实例化过程的完全控制。这在熔断降级场景中尤为重要——你可以根据运行时状态动态切换服务实现。
4.3.1 值 Provider:注入常量与配置
// 定义 Token
export const CONFIG_OPTIONS = 'CONFIG_OPTIONS';
// 模块中注册
@Module({
providers: [
{
provide: CONFIG_OPTIONS,
useValue: {
circuitBreakerTimeout: 5000,
maxRetries: 3,
},
},
],
})
export class AppModule {}
// 注入使用
@Injectable()
export class CircuitBreakerService {
constructor(@Inject(CONFIG_OPTIONS) private options: any) {}
}
4.3.2 类 Provider:运行时切换实现
在微服务中,你可能需要根据环境或配置切换熔断器实现。类 Provider 让这种切换变得优雅。
// 定义抽象接口
export interface ICircuitBreaker {
call(fn: () => Promise<any>): Promise<any>;
}
// 两种实现
export class SimpleCircuitBreaker implements ICircuitBreaker { /* ... */ }
export class AdvancedCircuitBreaker implements ICircuitBreaker { /* ... */ }
// 根据环境变量动态选择
@Module({
providers: [
{
provide: 'CIRCUIT_BREAKER',
useClass: process.env.NODE_ENV === 'production'
? AdvancedCircuitBreaker
: SimpleCircuitBreaker,
},
],
})
export class AppModule {}
4.4 工厂 Provider:动态创建与依赖组合
工厂 Provider 是 DI 系统中最强大的工具之一。它允许你注入其他 Provider,并在运行时执行任意逻辑来创建实例。这对于需要异步初始化、条件创建或复杂依赖组合的场景至关重要。
// 工厂 Provider 示例:创建带配置的 Redis 客户端
@Module({
providers: [
{
provide: 'REDIS_CLIENT',
useFactory: (configService: ConfigService, logger: Logger) => {
const options = configService.get('redis');
logger.log(`Connecting to Redis at ${options.host}:${options.port}`);
return new RedisClient(options);
},
inject: [ConfigService, Logger], // 工厂函数的依赖
},
],
})
export class RedisModule {}
inject数组中的 Provider 必须已在当前模块或导入模块中注册。- 工厂函数可以是同步的,也可以是异步的(返回 Promise)。
- 工厂函数中不要保留有状态副作用,保持幂等性。
4.5 异步 Provider:微服务启动的关键
微服务启动时,经常需要等待数据库连接、配置中心拉取、服务发现注册等异步操作完成。异步 Provider 允许 DI 容器在 Provider 就绪后再进行依赖注入,避免服务在未初始化完成时被调用。
// 异步 Provider:等待数据库连接
@Module({
providers: [
{
provide: 'DATABASE_CONNECTION',
useFactory: async (configService: ConfigService) => {
const connection = await createConnection(configService.get('db'));
return connection;
},
inject: [ConfigService],
},
],
})
export class DatabaseModule {}
// 使用异步 Provider 的服务
@Injectable()
export class UserService {
constructor(
@Inject('DATABASE_CONNECTION')
private dbConnection: Connection,
) {}
}
- 将数据库、消息队列、缓存等基础设施连接封装为异步 Provider。
- 结合
onApplicationBootstrap生命周期钩子,在应用完全启动后执行健康检查。 - 异步 Provider 失败时,应抛出明确异常,让 NestJS 的启动过程失败(fail-fast)。
- 考虑使用
@Optional()装饰器处理可选依赖,避免启动阻塞。
4.6 自定义 Provider 在熔断降级中的实战模式
结合熔断降级场景,自定义 Provider 可以实现运行时策略切换、动态配置注入、以及服务降级时的优雅回退。
// 熔断器策略工厂:根据熔断状态动态选择实现
@Module({
providers: [
{
provide: 'CIRCUIT_BREAKER_STRATEGY',
useFactory: (stateStore: CircuitBreakerStateStore) => {
// 根据当前熔断状态返回不同的策略实现
const state = stateStore.getCurrentState();
if (state === 'OPEN') {
return new FallbackStrategy(); // 熔断打开,直接返回降级结果
} else if (state === 'HALF_OPEN') {
return new ProbeStrategy(); // 半开状态,发送探测请求
} else {
return new NormalStrategy(); // 正常状态,执行完整调用
}
},
inject: [CircuitBreakerStateStore],
},
],
})
export class CircuitBreakerModule {}
4.7 Provider 作用域:单例、请求与瞬态
在微服务中,Provider 的生命周期管理直接影响资源使用和状态安全。NestJS 支持三种作用域:
| 作用域 | 装饰器 | 实例生命周期 | 微服务适用场景 |
|---|---|---|---|
| 单例(Singleton) | 默认 | 应用生命周期内唯一实例 | 无状态服务、配置、连接池 |
| 请求(Request) | @Injectable({ scope: Scope.REQUEST }) |
每个请求创建新实例 | 请求上下文、租户隔离 |
| 瞬态(Transient) | @Injectable({ scope: Scope.TRANSIENT }) |
每次注入创建新实例 | 状态敏感的辅助类、策略对象 |
4.8 本章小结
- Provider 是 DI 的核心抽象,支持值、类、工厂、别名四种注册方式。
- Module 通过 exports 控制 Provider 可见性,动态模块提供灵活的配置导入。
- 工厂 Provider 是实现熔断降级策略切换的关键,允许根据运行时状态动态选择实现。
- 异步 Provider 确保微服务基础设施就绪后再提供服务,避免启动时序问题。
- 作用域管理决定了 Provider 实例的生命周期,需根据场景谨慎选择。