在开发大型 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
-
- 在 模块导入时使用
// users.module.ts
@Module({
imports: [forwardRef(() => OrdersModule)], // 模块间转发
providers: [UsersService],
exports: [UsersService],
})
export class UsersModule {}
// orders.module.ts 同理
-
- 在模块导入时使用
// 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 的存在,它只需要发个广播。
- 安装包
pnpm i @nestjs/event-emitter
- 在根模块导入
import { Module } from '@nestjs/common';
import { EventEmitterModule } from '@nestjs/event-emitter';
@Module({
imports: [
EventEmitterModule.forRoot(), // 其他模块也在这里声明
],
})
export class AppModule {}
- 注入模块,并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);
}
}