在 NestJs 中配置依赖 DynamicModules 的最佳方法

Posted

技术标签:

【中文标题】在 NestJs 中配置依赖 DynamicModules 的最佳方法【英文标题】:Best way to configure dependent DynamicModules in NestJs 【发布时间】:2021-06-26 10:31:54 【问题描述】:

我一直在努力寻找处理导入在同一范围内具有相互依赖关系的 Nest 模块的最佳方法。

我的具体用例是一个大型 monorepo,其中包含多个微服务应用程序,这些应用程序需要一组通用组件(例如数据库、传输等)的特定配置/设置。我的目的是提供一个“汇总”模块来标准化配置过程。

问题在于模块导入数组中的模块似乎完全相互隔离。虽然我可以使用@Global,但它违背了模块级隔离的目的,坦率地说有点@Dirty

我尝试过的简化版本:

@Module()
export class ChaptersModule 
  static registerAsync(config: IChaptersAsyncConfig): DynamicModule 
    const provider = 
      provide: CHAPTERS,
      useFactory: config.useFactory,
      inject: config.inject || [],
    ;

    return 
      module: ChaptersModule,
      imports: [CharactersModule], // <-- another DynamicModule
      providers: [provider, ChaptersService],
      exports: [ChaptersService],
    ;
  

将该模块及其依赖项导入另一个模块时:

@Module(
  imports: [
    CharactersModule.forRootAsync(
      useFactory: () => characterData,
    ),
    ChaptersModule.registerAsync(
      useFactory: () => chapterData,
    ),
  ],
  controllers: [AppController],
  providers: [AppService],
)
export class AppModule 

Nest 实例化 CharactersModule 两次(一次用于***导入,一次用于 ChaptersModule 导入),即使 它们共享相同的消费模块范围。因为模块需要配置,Nest 实例化时没有它的配置提供程序,我得到了我最好的新朋友:

Error: Nest can't resolve dependencies of the ChaptersService (CHAPTERS, ?). Please make sure that the argument CharactersService at index [1] is available in the ChaptersModule context.

在这件事上花费的时间比我愿意公开分享的要多,我能找到的唯一解决方法是实例化 CharactersModule 并然后通过 requires?: any[] 属性将其传递给依赖模块添加到我的界面。

// Pre-Configure any dependent modules
export const configured = [
  CharactersModule.forRoot(characterData)
]

@Module(
  imports: [
    ...configured, // include configured modules in module imports
    ChaptersModule.registerAsync(
      requires: configured, // pass configured modules
      useFactory: () => chapterData
    )
  ],
  controllers: [AppController],
  providers: [AppService],
)
export class AppModule 

这感觉很hacky并且似乎有效,但我肯定错过了什么吗?示例 repo 是 here(我预计它的工作方式是在 broken 分支中。

处理场景的正确方法是什么?

【问题讨论】:

【参考方案1】:

I've got a Git repo that walks through the steps of how this is achievable with some clever RxJS,但我也会在这里给出一个高层次的概述。

所以我们的想法是你最终会为你的模块类添加几个 RxJS 操作符和属性;即一个Deferred 静态变量、一个私有静态timeout 变量和一个Subject 变量来保存模块配置。然后,在forRoot()forRootAsync() 内部,在返回配置之前,您最终会调用this.moduleConfig.next(configuration)。现在,您需要在应用程序中的任何其他位置调用 Module.Deferred 并获取 RxJS 延迟配置。这个Deferredrace 介于timeoutsubject.pipe(take(1)) 之间,这将允许您获得错误(从未调用过配置)或稍后获得配置。

@golevelup/nestjs-modules 有一个称为externallyConfigured() 的方法,它几乎可以为您处理上述问题。我强烈建议给我链接的 repo 阅读以了解模式,然后对我链接的包进行一次尝试,看看它需要多少照顾。

【讨论】:

谢谢杰。共享配置以外的资源的捆绑包(例如数据库模块)呢?我是否正确理解一个模块只会与直接后代共享 DI 范围,除非它是全局的? 我不确定我是否遵循了这个问题。 很公平 :) 我检查了你的 repo 和 golevelup 的,乍一看,它们似乎更适合共享配置。会挖得更深一些。感谢您的帮助!

以上是关于在 NestJs 中配置依赖 DynamicModules 的最佳方法的主要内容,如果未能解决你的问题,请参考以下文章

NestJS 缺少依赖

Nestjs 应用程序中`@ntegral/nestjs-sentry` 包的依赖注入问题

在 NestJS 中使用依赖注入导入 TypeScript 模块

在 Nestjs/GraphQL 中使用接口时避免循环依赖

如何在 NestJS 中跨模块全局注入价值?

以正确的方式避免循环依赖 - NestJS