Nodejs nestjs的循环引用问题

发布于 1周前 作者 sinazl 最后一次编辑是 5天前 来自 nodejs/Nestjs

Nodejs中nestjs的循环引用问题

我有几个 module ,service 引用结构如下

A -> B -> A. 也就是说 A ,B 有相互引用关系;这样会导致循环引用问题; nestjs 编译会报错;

通过查询,了解到的解决方法有两种;

  1. 抽离一个 module C ,然后引入 Service A ,B ; 在 A ,B controller 中直接引入 Service C ;那样 service A ,B 就会是单纯业务逻辑,不会引发 circular dependencies
  2. 是使用 forwardref

这两种方法都可以;但是有点疑问,希望能得到解惑;谢谢了;

方法 1: 会多出一个 module ; 那如果有关联关系的业务实体,是不是都会引发一个新的 module 来避免 circular dependencies

那我是不是可以把所有的 service 集中在一起 Module Common ,然后供 controller 消费,就能避免在 service 中消费其他 service 引发 circular dependencies ;这样的话 Module Common 就变成了垃圾桶了,啥都放在里面; 

方法 2: 有可能发生未知错误;不知道引用关系到底是什么样子的;跟 nestjs 的依赖注入 设计有点相反;不太想用


6 回复

最简单的方法是合并 A 和 B 为一个模块

理论上极少数情况会出现循环引用的情况,如果一旦出现基本说明两个模块耦合严重,这种情况合并就是了,至于你说的方法 1 其实是合并后的抽离可复用代码。


抽一个 C ,然后 A 和 B 的 Service (不是 Controller )都注入 C (不要直接 import ,走 NestJS 的 Injectable ),就像你 Service 会用 HttpService 一样搞

楼上两位朋友说的都对,我再补充一点, 还有一种方法,不引入 service 而是引入 reponstory ,

遇到过这种问题,使用的第一种方案,用 forwardref 我没跑成功。
看你说的只注入 repository ,我想是脱离问题本质了,本来就是想方便复用,这样相当于重写一遍逻辑了(也可以把数据库操作扩充到原有 repository 上,算是复用代码了吧)。
另外你说的抽到 Common 里,那为什么不一开始全写 Common 里,那样模块化就没有意义了,也不循环引用了。

控制反转有两种实现方案:依赖注入和依赖查找。nestjs 采用依赖注入导致模块之间代码耦合严重。本来模块化的意义就是实现代码隔离,但是 nestjs 实际上确是模块之间代码相互引用。因此,在 node 生态中,最便利的是依赖查找方案,可以实现模块之间的完全隔离,也就不会出现类似循环引用的问题,更不需要提出一个中间模块。可以参见 CabloyJS 全栈框架的《 Bean 容器与控制反转》: https://cabloy.com/zh-cn/articles/bean.html

在Node.js中使用NestJS框架时,循环引用是一个常见的问题,特别是在模块和服务之间。循环引用会导致应用程序启动失败,因为NestJS的依赖注入系统无法正确解析依赖关系。

解决方案

  1. 重新设计模块结构: 最直接的解决方案是重新设计你的模块结构,避免循环引用。确保每个模块只依赖于其他模块的输出,而不是内部实现。

  2. 使用forwardRef: 如果重新设计模块结构不可行,你可以使用forwardRef来告诉NestJS延迟解析依赖关系。

    import { Module, forwardRef } from '[@nestjs](/user/nestjs)/common';
    import { ServiceA } from './service-a';
    import { ServiceB } from './service-b';
    
    [@Module](/user/Module)({
      providers: [
        ServiceA,
        {
          provide: 'ServiceB',
          useClass: forwardRef(() => ServiceB),
        },
      ],
      exports: ['ServiceB'],
    })
    export class ModuleA {}
    
    [@Module](/user/Module)({
      imports: [forwardRef(() => ModuleA)],
      providers: [ServiceB],
    })
    export class ModuleB {}
    
  3. 延迟加载模块: 在某些情况下,你可以通过延迟加载模块来避免循环引用。这可以通过动态导入模块来实现。

    async function bootstrap() {
      const app = await NestFactory.create(AppModule);
      await app.init();
      const moduleB = await import('./module-b.module').then(m => m.ModuleB);
      app.registerModule(moduleB);
      await app.listen(3000);
    }
    bootstrap();
    

选择哪种方法取决于你的具体需求和应用程序的复杂性。

回到顶部