3、依赖注入基础:Provider与Token、自定义Provider、工厂模式与异步Provider
依赖注入(Dependency Injection,DI)是 NestJS 的核心运行时机制。在熔断降级与接口依赖反转场景中,我们需要在运行时动态替换、延迟初始化或条件注入不同的 Provider,这要求深入理解 Provider 的注册方式、Token 解析规则以及自定义 Provider 的几种高级模式。
3.1 Provider 与 Token 的基本概念
在 NestJS 中,Provider 是一个被 IoC 容器管理的类、值或工厂函数。Token 是容器用来查找和注入 Provider 的唯一标识符。默认情况下,Token 就是类的构造函数本身(类引用),但 Nest 允许使用字符串、Symbol 或自定义对象作为 Token。
| Token 类型 | 示例 | 适用场景 |
|---|---|---|
| 类引用(默认) | CatService |
标准单例服务 |
| 字符串 Token | 'CACHE_MANAGER' |
第三方库、跨模块共享 |
| Symbol Token | Symbol('REDIS_CLIENT') |
避免命名冲突 |
| 自定义对象 Token | { name: 'Config' } |
多例工厂、动态标识 |
关键理解: Token 是注入的"钥匙"。在熔断降级场景中,我们经常用字符串 Token 来标记"熔断器实例"或"降级策略",然后在运行时通过不同的 Provider 绑定来切换实现。
3.2 标准 Provider 与自定义 Provider
标准 Provider 使用 @Injectable() 装饰器,Nest 自动将其注册到容器。自定义 Provider 则通过模块的 providers 数组显式声明,支持四种形态:
- useClass — 指定一个类作为 Provider,可替换默认实现
- useValue — 注入一个固定值或对象
- useFactory — 通过工厂函数动态创建实例
- useExisting — 为已有 Provider 创建别名
// 标准 Provider:自动注册
@Injectable()
export class PaymentService {
process(amount: number) { /* ... */ }
}
// 自定义 Provider:显式声明
@Module({
providers: [
{
provide: 'PAYMENT_SERVICE',
useClass: PaymentService,
},
{
provide: 'API_KEY',
useValue: 'sk-abc123',
},
],
})
export class PaymentModule {}
熔断场景应用: 使用
useClass 可以在测试或降级时,将 PaymentService 替换为 MockPaymentService 或 FallbackPaymentService,只需修改 Provider 配置,无需改动任何消费代码。
3.3 工厂模式与异步 Provider
工厂模式(useFactory)是依赖反转设计的核心工具。它允许在 Provider 创建时执行任意逻辑,包括条件判断、配置读取、异步初始化等。异步 Provider 则通过 async 工厂函数实现,容器会等待 Promise resolve 后才注入。
// 同步工厂:根据环境变量选择实现
{
provide: 'CIRCUIT_BREAKER',
useFactory: (configService: ConfigService) => {
const threshold = configService.get('FAILURE_THRESHOLD', 5);
return new CircuitBreaker(threshold);
},
inject: [ConfigService],
}
// 异步工厂:等待外部资源就绪
{
provide: 'REDIS_CLIENT',
useFactory: async () => {
const client = new Redis({ host: 'localhost', port: 6379 });
await client.connect(); // 等待连接完成
return client;
},
}
| 工厂类型 | 返回值 | 注入时机 | 典型用途 |
|---|---|---|---|
| 同步工厂 | 直接返回实例 | 模块初始化时 | 配置驱动、策略选择 |
| 异步工厂 | 返回 Promise | Promise resolve 后 | 数据库连接、远程配置拉取 |
| 条件工厂 | 根据条件返回不同实现 | 模块初始化时 | A/B 测试、熔断降级切换 |
注意: 异步 Provider 的初始化顺序由容器管理。如果多个异步 Provider 之间存在依赖关系,必须通过
inject 数组显式声明,否则可能导致注入未就绪的实例。
3.4 依赖注入与熔断降级的结合模式
在熔断降级架构中,依赖注入的灵活性直接决定了系统的可维护性。以下 SVG 展示了基于自定义 Provider 的熔断器注入架构:
3.5 实战:为熔断器创建可切换的 Provider
以下代码展示了如何利用自定义 Provider 实现熔断器的运行时切换:
// circuit-breaker.provider.ts
import { Provider } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { CircuitBreaker } from './circuit-breaker';
import { MockCircuitBreaker } from './mock-circuit-breaker';
export const CircuitBreakerProvider: Provider = {
provide: 'CIRCUIT_BREAKER',
useFactory: (config: ConfigService) => {
const useMock = config.get('USE_MOCK_CIRCUIT_BREAKER', false);
if (useMock) {
return new MockCircuitBreaker();
}
return new CircuitBreaker({
failureThreshold: config.get('FAILURE_THRESHOLD', 5),
recoveryTimeout: config.get('RECOVERY_TIMEOUT', 30000),
});
},
inject: [ConfigService],
};
// 在模块中注册
@Module({
providers: [CircuitBreakerProvider],
exports: ['CIRCUIT_BREAKER'],
})
export class ResilienceModule {}
依赖反转效果:
OrderService 和 PaymentService 只依赖 Token 'CIRCUIT_BREAKER',不依赖具体实现类。通过修改环境变量 USE_MOCK_CIRCUIT_BREAKER,可以在测试和生产环境之间无缝切换,无需修改任何业务代码。
3.6 异步 Provider 在熔断场景中的典型应用
当熔断器的配置需要从远程配置中心(如 Consul、etcd)拉取时,异步 Provider 是唯一正确的选择:
// 异步配置拉取工厂
{
provide: 'CIRCUIT_BREAKER_CONFIG',
useFactory: async (configClient: ConfigClient) => {
const remoteConfig = await configClient.fetch('circuit-breaker');
return {
failureThreshold: remoteConfig.failureThreshold ?? 5,
recoveryTimeout: remoteConfig.recoveryTimeout ?? 30000,
halfOpenMaxRequests: remoteConfig.halfOpenMaxRequests ?? 3,
};
},
inject: [ConfigClient],
}
// 依赖该配置的熔断器工厂
{
provide: 'CIRCUIT_BREAKER',
useFactory: async (config: CircuitBreakerConfig) => {
const breaker = new CircuitBreaker(config);
await breaker.initialize(); // 异步初始化
return breaker;
},
inject: ['CIRCUIT_BREAKER_CONFIG'],
}
最佳实践: 异步 Provider 的
inject 数组顺序必须与工厂函数参数顺序一致。如果某个依赖也是异步 Provider,容器会自动等待其 resolve 后再调用当前工厂。
3.7 本章小结
- Token 是注入的钥匙:类引用、字符串、Symbol 均可作为 Token,字符串 Token 最适合熔断降级场景的跨模块共享。
- 自定义 Provider 提供灵活性:
useClass实现实现替换,useValue注入配置,useFactory支持动态创建。 - 工厂模式实现依赖反转:通过工厂函数,消费方只依赖 Token,不依赖具体实现,符合依赖倒置原则。
- 异步 Provider 处理初始化等待:远程配置、数据库连接等异步资源通过
async useFactory安全注入。 - 熔断器注入架构:模块 → 服务 → 熔断器 → 工厂的四层结构,每层通过 Token 解耦,支持运行时切换。