NestJS:如何注册瞬态和每个 Web 请求提供程序

orl*_*aqp 7 node.js typescript nestjs

我正在使用 NestJS 开发一个多租户应用程序,并通过他们的 GraphQL 模块提供我的 API。我想知道如何告诉 NestJS 为每个 Web 请求实例化我的提供程序。根据他们的文档,默认情况下提供程序是单例的,但我找不到注册瞬态或每个请求提供程序的方法。

让我解释一个具体的用例。在我的多租户实现中,我每个客户都有一个数据库,每次我在后端收到请求时,我都需要找出它是哪个客户的,所以我需要通过连接到正确的数据库来实例化服务。

这甚至可以使用 NestJS 吗?

Kim*_*ern 9

随着 nest.js 6.0 的发布,添加了注入范围。有了这个,您可以为您的提供者选择以下三个范围之一:

  • SINGLETON:默认行为。您的提供程序的一个实例用于整个应用程序
  • TRANSIENT:为每个注入它的提供者创建一个专用的提供者实例。
  • REQUEST:对于每个请求,都会创建一个新的提供者。注意:此行为会在您的依赖链中冒泡。示例:如果 UsersController (Singleton) 注入了注入 OtherService (Request) 的 UsersService (Singleton),那么 UsersController 和 UsersService 都将自动变为请求范围。

用法

要么将其添加到@Injectable()装饰器:

@Injectable({ scope: Scope.REQUEST })
export class UsersService {}
Run Code Online (Sandbox Code Playgroud)

或者在你的模块定义中为自定义提供者设置它:

{
  provide: 'CACHE_MANAGER',
  useClass: CacheManager,
  scope: Scope.TRANSIENT,
}
Run Code Online (Sandbox Code Playgroud)

过时的答案

正如你在这个issue 中看到的,nestjs 还没有为请求范围的提供者提供一个内置的解决方案。但它可能会在不久的将来这样做:

一旦 async-hooks 功能(它在节点 10 中仍处于试验阶段)稳定,我们将考虑为请求范围的实例提供内置解决方案。

  • 是的,我已经看到了很多讨论,同时我不得不切换到“带有租户标识符的单个数据库”以继续使用我认为很棒的 NestJS,但它仍然缺少这一点,在我看来这是关键 (2认同)

Mar*_*mek 5

我正在为类似的问题苦苦挣扎,实现这一目标的一种方法是将node-request-context模块用作全局请求寄存器,这将为您提供请求上下文。所以你不会有单独的服务实例,但你可以要求这个静态寄存器给你请求特定的实例/连接。

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;
    }

}
Run Code Online (Sandbox Code Playgroud)

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);
    };
  }

}
Run Code Online (Sandbox Code Playgroud)

然后在您的 Nest 应用程序中注册新的中间件:

const app = await NestFactory.create(AppModule, {});
app.use(app.get(RequestLoggerMiddleware).resolve());
Run Code Online (Sandbox Code Playgroud)

最后是利润部分 - 在您的应用程序中的任何位置获取特定于请求的连接:

const conn = RequestContext.getConnection();
Run Code Online (Sandbox Code Playgroud)