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 的熔断器注入架构:

基于自定义 Provider 的熔断器注入架构 OrderModule PaymentModule NotificationModule OrderService PaymentService NotificationService CircuitBreaker (Token: 'CB') FallbackStrategy (Token: 'FALLBACK') useFactory: 根据配置动态创建熔断器实例 各模块通过字符串 Token 'CB' 和 'FALLBACK' 注入熔断器,工厂层负责实例化策略

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 解耦,支持运行时切换。