NestJS 彻底搞定循环依赖

21 阅读3分钟

在开发大型 NestJS 应用时,你可能会遇到这样的报错 A circular dependency between modules..., 这标识着你又遇到了一个你需要解决的问题,循环依赖(Circular Dependency), 简单来说就是 Service A 需要 Service B,而 Service B 又反手依赖了 Service A,只是它不是 "你爱我 我爱你, xxx城甜蜜蜜"

循环依赖的情况

循环可能是 2 个,也可能是三个的情况,就像 ♻️ 这个标识是三个之间造成了循环,我们来看两个相爱相杀的循环服务 假设我们有两个服务:UsersService(处理用户信息)和 OrdersService(处理订单)。

当用户注销时,需要调用 OrdersService 删除其订单。

当订单创建时,需要调用 UsersService 更新用户的积分。

// ❌❌ 错误的示范
// users.service.ts
@Injectable()
export class UsersService {
  constructor(private readonly ordersService: OrdersService) {} // 依赖 Orders
}

// orders.service.ts
@Injectable()
export class OrdersService {
  constructor(private readonly usersService: UsersService) {} // 依赖 Users
}

结果: NestJS 在启动时会因为无法确定先实例化谁而直接崩溃。

解决方案1: forwardRef 转发引用

forwardRef(() => XXX)(转发引用)允许 Nest 引用一个当前尚未定义的类。它通过一个延迟执行的函数来解决初始化顺序问题,因此需要在 模块导入构造函数注入的时候都是用 forwardRef

    1. 在 模块导入时使用
// users.module.ts
@Module({
  imports: [forwardRef(() => OrdersModule)], // 模块间转发
  providers: [UsersService],
  exports: [UsersService],
})
export class UsersModule {}

// orders.module.ts 同理
    1. 在模块导入时使用
// users.service.ts
@Injectable()
export class UsersService {
  constructor(
    @Inject(forwardRef(() => OrdersService)) // 使用 forwardRef 包裹
    private readonly ordersService: OrdersService,
  ) {}
}

// orders.service.ts 同理

解决方案2: 架构规避

循环依赖通常是设计的问题,因此最好的解决方法不是“修复”它,而是“解决”它。

提取公共逻辑

如果 A 和 B 互相依赖,往往是因为它们共同依赖了一部分中间逻辑。

创建一个 CommonService,将 A 和 B 互相调用的那部分代码抽离出来, 比如 user 和 order 之间依赖一个公共的方法

// vipUtils.service.ts
@Injectable()
export class VipUtilsService {
  // 将原本可能导致循环的逻辑放在这里
  calculateDiscount(level: string, price: number): number {
    return level === 'VIP' ? price * 0.8 : price;
  }
}

// vipUtils.module.ts
@Module({
  imports: [],
  controllers: [],
  providers: [VipUtilsService],
  exports: [VipUtilsService], // 导出服务,供其他模块使用
})
export class VipUtilsModule {}


// users.module.ts
@Module({
  imports: [VipUtilsModule],// 这样可以使用 VipUtilsModule exports 的服务
  providers: [UsersService],
  exports: [UsersService],
})
export class UsersModule {}

// orders.module.ts 同理
事件驱动 (Event-Based),解耦相互依赖

这是解耦最高级的做法,真正的实现 "高内聚,低耦合",UsersService 不需要知道 OrdersService 的存在,它只需要发个广播。

  1. 安装包
pnpm i @nestjs/event-emitter
  1. 在根模块导入
import { Module } from '@nestjs/common';
import { EventEmitterModule } from '@nestjs/event-emitter';

@Module({
  imports: [
    EventEmitterModule.forRoot(), // 其他模块也在这里声明
  ],
})
export class AppModule {}
  1. 注入模块,并emit、 监听事件
// user.service.ts
import { Injectable } from '@nestjs/common';
import { EventEmitter2 } from '@nestjs/event-emitter';

@Injectable()
export class UserService {
  constructor(private readonly eventEmitter: EventEmitter2) {}

  deleteUser(userId: string, orderId: string) {
    // 只负责触发事件
    this.eventEmitter.emit('user.order.delete', userId, orderId);
  }
}

// order.service.ts
@Injectable()
export class OrderService {
  constructor(private readonly eventEmitter: EventEmitter2) {}

  // 监听用户删除订单事件
  @OnEvent('user.order.delete')
  handleOrderDeleteByEvent(userId: number, orderId: number) {
    console.log('handle order delete by event', userId, orderId);
  }
}

解决循环依赖方案的对比图

扫码_搜索联合传播样式-标准色版.png