You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

NestJS v9 持久化提供者实现:请求头切换依赖疑问

实现NestJS持久化提供者按请求头切换实例

1. 自定义ContextIdStrategy绑定请求头与上下文子树

首先要实现ContextIdStrategy,将请求头x-provider-id作为上下文子树的唯一标识,确保同一标识的请求复用同一上下文实例:

import { ContextId, ContextIdStrategy } from '@nestjs/core';
import { Request } from 'express';
import { AsyncLocalStorage } from 'async_hooks';

// 初始化异步本地存储,用于传递上下文标识
export const als = new AsyncLocalStorage<{ subtreeId: string }>();

export class ProviderContextStrategy implements ContextIdStrategy {
  attach(contextId: ContextId, request: Request) {
    // 从请求头获取提供者标识,默认用'default'兜底
    const providerId = (request.headers['x-provider-id'] as string) ?? 'default';
    // 将标识存入异步本地存储,供后续提供者工厂使用
    als.enterWith({ subtreeId: providerId });
    
    // 返回持久化上下文配置,同一subtreeId复用实例
    return {
      contextId,
      getSubTreeId: () => providerId,
    };
  }
}

在main.ts中注册该策略:

import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { ContextIdFactory } from '@nestjs/core';
import { ProviderContextStrategy } from './provider-context.strategy';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  // 应用自定义上下文策略
  ContextIdFactory.applyStrategy(new ProviderContextStrategy());
  await app.listen(3000);
}
bootstrap();

2. 配置持久化提供者与动态切换逻辑

先定义接口与具体实现:

// test.interface.ts
export interface ITest {
  execute(): string;
}

// test1.provider.ts
export class Test1Provider implements ITest {
  execute() {
    return '来自Test1Provider的响应';
  }
}

// test2.provider.ts
export class Test2Provider implements ITest {
  execute() {
    return '来自Test2Provider的响应';
  }
}

在模块中配置动态提供者,核心是开启durable: true实现实例复用,并根据上下文标识切换提供者:

import { Module, Inject } from '@nestjs/common';
import { ITest, Test1Provider, Test2Provider } from './providers';
import { als } from './provider-context.strategy';

@Module({
  providers: [
    // 注册两个具体提供者
    Test1Provider,
    Test2Provider,
    {
      provide: ITest,
      useFactory: (test1: Test1Provider, test2: Test2Provider) => {
        // 从异步本地存储获取当前上下文的提供者标识
        const subtreeId = als.getStore()?.subtreeId;
        // 根据标识返回对应提供者实例
        switch (subtreeId) {
          case '1':
            return test1;
          case '2':
            return test2;
          default:
            return test1;
        }
      },
      inject: [Test1Provider, Test2Provider],
      // 关键:标记为持久化提供者,同一subtreeId复用实例
      durable: true,
    },
  ],
  exports: [ITest],
})
export class TestModule {}

3. 业务服务无感知使用

业务服务直接注入ITest接口,无需关心具体实现:

import { Injectable, Inject } from '@nestjs/common';
import { ITest } from './providers';

@Injectable()
export class BusinessService {
  constructor(@Inject(ITest) private readonly testProvider: ITest) {}

  handleRequest() {
    return this.testProvider.execute();
  }
}

关键注意事项

  • 必须确保durable: true已添加到ITest的提供者配置中,否则即使上下文标识相同,也会每次请求创建新实例。
  • 若Test1Provider/Test2Provider本身依赖请求作用域服务,需将这些依赖也标记为durable: true,否则会破坏实例复用逻辑。
  • 异步本地存储(ALS)用于在请求生命周期内传递上下文标识,避免直接依赖REQUEST对象可能带来的场景限制。

内容的提问来源于stack exchange,提问作者mago

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.11 22:46:03