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
相关产品推荐
相关产品推荐

