You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

NestJS六边形架构下依赖倒置的实现问题

NestJS依赖倒置+六边形架构的依赖注入问题解决方案

问题背景

在NestJS中尝试落地依赖倒置原则与六边形架构,设计了四层模块结构,但当前实现出现依赖注入报错,若直接在应用模块导入数据库模块会破坏依赖倒置原则,需寻求合规解决方案。

预期架构目标

  • 根模块:AppModule,负责整合所有模块
  • 数据库模块:InfraDatabaseModule,不导入其他模块,仅由根模块直接导入
  • REST模块:InfraRestModule,导入应用模块以让控制器调用其中服务
  • 应用模块:ApplicationModule,作为最内层模块不导入其他模块,通过Repository接口操作实体,无需知晓接口具体实现(依赖倒置)

现有实现代码

app.module.ts

@Module({
  imports: [
      InfraRestModule, // API请求入口
      InfraDatabaseModule, // 实现Repository接口
  ],
})
export class AppModule {}

infra-rest.module.ts

@Module({
  imports: [ApplicationModule],
  controllers: [SomeController], // 控制器注入应用模块的服务
})
export class InfraRestModule {}

infra-database.module.ts

@Module({
  providers: [{ provide: MyRepoSymbol, useClass: MyRepoImpl }],
  exports: [MyRepoSymbol],
})
export class InfraDatabaseModule {}

application.module.ts

@Module({
  providers: [SomeService],
  exports: [SomeService],
})
export class ApplicationModule {}

// 模块内其他文件
@Injectable()
export class SomeService {
  constructor(@Inject(MyRepoSymbol) private readonly repo: MyRepo) {}
  // ...
}

interfaces/my-repo.ts

export interface MyRepo {
  // ...
}
export const MyRepoSymbol = Symbol('MyRepoSymbol');

报错信息

UnknownDependenciesException [Error]: Nest can't resolve dependencies of the SomeService (?). Please make sure that the argument Symbol(MyRepoSymbol) at index [0] is available in the ApplicationModule context.

Potential solutions:
- Is ApplicationModule a valid NestJS module?
- If Symbol(MyRepoSymbol) is a provider, is it part of the current ApplicationModule?
- If Symbol(MyRepoSymbol) is exported from a separate @Module, is that module imported within ApplicationModule?
  @Module({
    imports: [ /* the Module containing Symbol(HELLO_TEST) */ ]
  })

解决方案

核心思路是利用NestJS的模块作用域与导出机制,让根模块作为基础设施层与应用层的桥梁,既满足依赖注入的上下文要求,又保持应用层的独立性。

方案1:根模块中转基础设施提供者(推荐)

修改AppModule导出数据库模块,让InfraRestModule通过导入根模块获取基础设施提供者:

修改后的app.module.ts

@Module({
  imports: [InfraDatabaseModule],
  exports: [InfraDatabaseModule], // 导出数据库模块,使导入根模块的模块能访问其提供者
})
export class AppModule {}

修改后的infra-rest.module.ts

@Module({
  imports: [AppModule, ApplicationModule], // 同时导入根模块(获取数据库实现)和应用模块
  controllers: [SomeController],
})
export class InfraRestModule {}

此时ApplicationModule无需导入任何基础设施模块,SomeService能在InfraRestModule的上下文里获取到MyRepoSymbol的实现,完全符合依赖倒置原则。

方案2:标记数据库模块为全局模块

在InfraDatabaseModule上添加@Global()装饰器,使其导出的提供者在全应用范围内可用:

修改后的infra-database.module.ts

@Global() // 标记为全局模块
@Module({
  providers: [{ provide: MyRepoSymbol, useClass: MyRepoImpl }],
  exports: [MyRepoSymbol],
})
export class InfraDatabaseModule {}

这种方式无需修改其他模块,但全局模块需谨慎使用,避免过度耦合。

方案3:创建基础设施聚合模块(适用于大型项目)

新建InfraModule统一管理所有基础设施模块,再由根模块导出:

infra.module.ts

@Module({
  imports: [InfraDatabaseModule],
  exports: [InfraDatabaseModule],
})
export class InfraModule {}

修改后的app.module.ts

@Module({
  imports: [InfraModule],
  exports: [InfraModule],
})
export class AppModule {}

修改后的infra-rest.module.ts

@Module({
  imports: [AppModule, ApplicationModule],
  controllers: [SomeController],
})
export class InfraRestModule {}

该方案能更清晰地划分基础设施层边界,适合架构复杂的项目。

关键原理

NestJS的模块作用域是层级化的:当模块A导入模块B时,模块A及其子模块可访问模块B导出的提供者。通过根模块作为中间层,既能让应用层获取到基础设施实现,又能保证应用层不依赖具体实现,完美契合依赖倒置原则与六边形架构的核心要求。

内容的提问来源于stack exchange,提问作者Bram Meerten

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.12 05:39:58