NestJS:如何注册瞬态和每个 Web 请求提供程序
NestJS: How to register transient and per web request providers
我正在使用 NestJS 开发一个多租户应用程序,并通过他们的 GraphQL 模块提供我的 API。我想知道如何告诉 NestJS 根据 Web 请求实例化我的提供程序。根据他们的文档,默认情况下提供程序是单例的,但我找不到注册瞬态或按请求提供程序的方法。
让我解释一个具体的用例。在我的多租户实现中,每个客户都有一个数据库,每次我在后端收到请求时,我都需要找出它是针对哪个客户的,因此我需要通过连接到正确的数据库来实例化服务。
这甚至可以使用 NestJS 吗?
我一直在努力解决类似的问题,实现此目的的一种方法是使用 node-request-context
模块作为全局请求寄存器,这将为您提供请求上下文。所以你不会有单独的服务实例,但你可以要求这个静态寄存器给你特定的请求 instance/connection.
https://github.com/guyguyon/node-request-context
创建简单的上下文助手:
import { createNamespace, getNamespace } from 'node-request-context';
import * as uuid from 'uuid';
export class RequestContext {
public static readonly NAMESPACE = 'some-namespace';
public readonly id = uuid.v4();
constructor(public readonly conn: Connection) { }
static create(conn: Connection, next: Function) {
const context = new RequestContext(conn);
const namespace = getNamespace(RequestContext.NAMESPACE) || createNamespace(RequestContext.NAMESPACE);
namespace.run(() => {
namespace.set(RequestContext.name, context);
next();
});
}
static currentRequestContext(): RequestContext {
const namespace = getNamespace(RequestContext.NAMESPACE);
return namespace ? namespace.get(RequestContext.name) : null;
}
static getConnection(): Connection {
const context = RequestContext.currentRequestContext();
return context ? context.conn : null;
}
}
conn
实例参数是您的连接,请随意将其他请求特定的依赖项放在那里。还有 id
只是为了调试,不需要像我一样使用 uuid
模块。
创建中间件包装器(这允许您在此处使用 DI):
@Injectable()
export class ContextMiddleware implements NestMiddleware {
constructor(private readonly connectionManager: ...) { }
resolve(...args: any[]): MiddlewareFunction {
return (req, res, next) => {
// create the request specific connection here, probably based on some auth header...
RequestContext.create(this.connectionManager.createConnection(), next);
};
}
}
然后在您的嵌套应用程序中注册新的中间件:
const app = await NestFactory.create(AppModule, {});
app.use(app.get(RequestLoggerMiddleware).resolve());
最后是利润部分 - 在您的应用程序中的任何位置获取特定于请求的连接:
const conn = RequestContext.getConnection();
随着 nest.js 6.0 的发布,添加了 injection scopes。有了这个,您可以为您的提供商选择以下三个范围之一:
- SINGLETON:默认行为。您的提供商的一个实例用于整个应用程序
- TRANSIENT:为注入它的每个提供程序创建一个专用实例。
- REQUEST:对于每个请求,都会创建一个新的提供者。注意:此行为会在您的依赖链中冒泡。示例:如果 UsersController (Singleton) 注入 UsersService (Singleton),而 UsersService (Singleton) 注入 OtherService (Request),则 UsersController 和 UsersService 将自动成为请求范围的。
用法
要么将其添加到 @Injectable()
装饰器中:
@Injectable({ scope: Scope.REQUEST })
export class UsersService {}
或者在您的模块定义中为自定义提供者设置它:
{
provide: 'CACHE_MANAGER',
useClass: CacheManager,
scope: Scope.TRANSIENT,
}
过时的答案
正如您在此 issue 中所见,nestjs 尚未为请求范围的提供程序提供内置解决方案。但它可能会在不久的将来这样做:
Once async-hooks feature (it is still experimental in node 10) is
stable, we'll think about providing a built-in solution for
request-scoped instances.
NestJS 有一个内置的请求范围依赖注入机制
https://docs.nestjs.com/fundamentals/injection-scopes
但根据文档,它有严重的缺点:
Scope bubbles up the injection chain. A controller that depends on a request-scoped provider will, itself, be request-scoped.
Using request-scoped providers will have an impact on application performance. While Nest tries to cache as much metadata as possible, it will still have to create an instance of your class on each request. Hence, it will slow down your average response time and overall benchmarking result. Unless a provider must be request-scoped, it is strongly recommended that you use the default singleton scope.
最近我为 NestJS 创建了请求范围的实现,没有冒泡注入链和性能影响。
https://github.com/kugacz/nj-request-scope
要首先使用它,您必须在模块 class 装饰器中添加 RequestScopeModule 的导入:
import { RequestScopeModule } from 'nj-request-scope';
@Module({
imports: [RequestScopeModule],
})
接下来request-scope注入有两种方式:
- 使用 NJRS_REQUEST 令牌将快速请求对象注入 class 构造函数:
import { NJRS_REQUEST } from 'nj-request-scope';
[...]
constructor(@Inject(NJRS_REQUEST) private readonly request: Request) {}
- 更改 class 使用 @RequestScope() 装饰器将范围注入请求范围:
import { RequestScope } from 'nj-request-scope';
@Injectable()
@RequestScope()
export class RequestScopeService {
您可以在此存储库中找到示例实现:https://github.com/kugacz/nj-request-scope-example
我正在使用 NestJS 开发一个多租户应用程序,并通过他们的 GraphQL 模块提供我的 API。我想知道如何告诉 NestJS 根据 Web 请求实例化我的提供程序。根据他们的文档,默认情况下提供程序是单例的,但我找不到注册瞬态或按请求提供程序的方法。
让我解释一个具体的用例。在我的多租户实现中,每个客户都有一个数据库,每次我在后端收到请求时,我都需要找出它是针对哪个客户的,因此我需要通过连接到正确的数据库来实例化服务。
这甚至可以使用 NestJS 吗?
我一直在努力解决类似的问题,实现此目的的一种方法是使用 node-request-context
模块作为全局请求寄存器,这将为您提供请求上下文。所以你不会有单独的服务实例,但你可以要求这个静态寄存器给你特定的请求 instance/connection.
https://github.com/guyguyon/node-request-context
创建简单的上下文助手:
import { createNamespace, getNamespace } from 'node-request-context';
import * as uuid from 'uuid';
export class RequestContext {
public static readonly NAMESPACE = 'some-namespace';
public readonly id = uuid.v4();
constructor(public readonly conn: Connection) { }
static create(conn: Connection, next: Function) {
const context = new RequestContext(conn);
const namespace = getNamespace(RequestContext.NAMESPACE) || createNamespace(RequestContext.NAMESPACE);
namespace.run(() => {
namespace.set(RequestContext.name, context);
next();
});
}
static currentRequestContext(): RequestContext {
const namespace = getNamespace(RequestContext.NAMESPACE);
return namespace ? namespace.get(RequestContext.name) : null;
}
static getConnection(): Connection {
const context = RequestContext.currentRequestContext();
return context ? context.conn : null;
}
}
conn
实例参数是您的连接,请随意将其他请求特定的依赖项放在那里。还有 id
只是为了调试,不需要像我一样使用 uuid
模块。
创建中间件包装器(这允许您在此处使用 DI):
@Injectable()
export class ContextMiddleware implements NestMiddleware {
constructor(private readonly connectionManager: ...) { }
resolve(...args: any[]): MiddlewareFunction {
return (req, res, next) => {
// create the request specific connection here, probably based on some auth header...
RequestContext.create(this.connectionManager.createConnection(), next);
};
}
}
然后在您的嵌套应用程序中注册新的中间件:
const app = await NestFactory.create(AppModule, {});
app.use(app.get(RequestLoggerMiddleware).resolve());
最后是利润部分 - 在您的应用程序中的任何位置获取特定于请求的连接:
const conn = RequestContext.getConnection();
随着 nest.js 6.0 的发布,添加了 injection scopes。有了这个,您可以为您的提供商选择以下三个范围之一:
- SINGLETON:默认行为。您的提供商的一个实例用于整个应用程序
- TRANSIENT:为注入它的每个提供程序创建一个专用实例。
- REQUEST:对于每个请求,都会创建一个新的提供者。注意:此行为会在您的依赖链中冒泡。示例:如果 UsersController (Singleton) 注入 UsersService (Singleton),而 UsersService (Singleton) 注入 OtherService (Request),则 UsersController 和 UsersService 将自动成为请求范围的。
用法
要么将其添加到 @Injectable()
装饰器中:
@Injectable({ scope: Scope.REQUEST })
export class UsersService {}
或者在您的模块定义中为自定义提供者设置它:
{
provide: 'CACHE_MANAGER',
useClass: CacheManager,
scope: Scope.TRANSIENT,
}
过时的答案
正如您在此 issue 中所见,nestjs 尚未为请求范围的提供程序提供内置解决方案。但它可能会在不久的将来这样做:
Once async-hooks feature (it is still experimental in node 10) is stable, we'll think about providing a built-in solution for request-scoped instances.
NestJS 有一个内置的请求范围依赖注入机制
https://docs.nestjs.com/fundamentals/injection-scopes
但根据文档,它有严重的缺点:
Scope bubbles up the injection chain. A controller that depends on a request-scoped provider will, itself, be request-scoped.
Using request-scoped providers will have an impact on application performance. While Nest tries to cache as much metadata as possible, it will still have to create an instance of your class on each request. Hence, it will slow down your average response time and overall benchmarking result. Unless a provider must be request-scoped, it is strongly recommended that you use the default singleton scope.
最近我为 NestJS 创建了请求范围的实现,没有冒泡注入链和性能影响。
https://github.com/kugacz/nj-request-scope
要首先使用它,您必须在模块 class 装饰器中添加 RequestScopeModule 的导入:
import { RequestScopeModule } from 'nj-request-scope';
@Module({
imports: [RequestScopeModule],
})
接下来request-scope注入有两种方式:
- 使用 NJRS_REQUEST 令牌将快速请求对象注入 class 构造函数:
import { NJRS_REQUEST } from 'nj-request-scope';
[...]
constructor(@Inject(NJRS_REQUEST) private readonly request: Request) {}
- 更改 class 使用 @RequestScope() 装饰器将范围注入请求范围:
import { RequestScope } from 'nj-request-scope';
@Injectable()
@RequestScope()
export class RequestScopeService {
您可以在此存储库中找到示例实现:https://github.com/kugacz/nj-request-scope-example