NestJS环境下的熔断降级与接口依赖反转设计 - 第2节
2.1 使用 NestJS CLI 创建项目
NestJS 官方 CLI 是初始化项目最标准、最快速的方式。它自动生成 TypeScript 编译配置、ESLint 规则、测试框架以及推荐的目录骨架,让开发者从零配置中解放出来。
2.1.1 安装 CLI 与创建项目
# 全局安装 NestJS CLI
npm install -g @nestjs/cli
# 创建新项目(项目名:circuit-breaker-demo)
nest new circuit-breaker-demo
执行 nest new 后,CLI 会询问包管理器选择(npm / yarn / pnpm)。选择后自动完成依赖安装与 Git 初始化。
2.1.2 生成后的目录结构
circuit-breaker-demo/
├── src/
│ ├── app.controller.spec.ts # 单元测试
│ ├── app.controller.ts # 根控制器
│ ├── app.module.ts # 根模块
│ ├── app.service.ts # 根服务
│ └── main.ts # 应用入口
├── test/
│ ├── app.e2e-spec.ts # 端到端测试
│ └── jest-e2e.json
├── .eslintrc.js
├── .prettierrc
├── nest-cli.json
├── package.json
├── tsconfig.build.json
└── tsconfig.json
提示:NestJS CLI 生成的
src/ 目录是纯模块化结构,所有业务代码都按模块组织,不推荐在根目录下平铺文件。
2.2 模块化结构解析
NestJS 的模块化设计借鉴了 Angular 的模块系统,每个功能单元被封装在一个 @Module 装饰器中。模块是组织代码、管理依赖、控制作用域的最小单元。
2.2.1 模块的核心组成
| 元素 | 装饰器/类 | 职责 |
|---|---|---|
| 控制器 | @Controller() |
处理入站 HTTP 请求,路由分发 |
| 服务 | @Injectable() |
封装业务逻辑,可被注入到控制器或其他服务 |
| 模块 | @Module() |
组装控制器、服务、导入/导出其他模块 |
| 提供者 | @Injectable() |
可被注入的任意类(服务、仓库、工厂等) |
2.2.2 模块的元数据属性
@Module({
imports: [], // 导入其他模块,共享其导出的提供者
controllers: [], // 注册本模块的控制器
providers: [], // 注册本模块的服务/提供者
exports: [], // 导出本模块的提供者,供其他模块使用
})
export class AppModule {}
关键理解:模块的
exports 属性是实现依赖反转的基础——模块只暴露必要的服务接口,隐藏内部实现细节。
2.3 依赖注入容器基础概念
NestJS 内置了强大的依赖注入(DI)容器。DI 容器负责实例化对象、管理生命周期、自动解析构造函数中的依赖关系。这是实现熔断降级与接口反转设计的核心基础设施。
2.3.1 依赖注入的工作流程
- 注册提供者:在模块的
providers数组中声明类或令牌。 - 声明依赖:在构造函数中通过类型注解或
@Inject()声明需要注入的依赖。 - 容器解析:NestJS 运行时扫描构造函数参数,从容器中查找匹配的提供者实例。
- 注入实例:容器将解析后的实例注入到构造函数中,完成对象组装。
2.3.2 提供者注册方式对比
| 注册方式 | 语法 | 适用场景 |
|---|---|---|
| 标准类提供者 | providers: [UserService] |
最常用,自动使用类名作为令牌 |
| 自定义令牌 | { provide: 'CACHE', useClass: RedisCache } |
接口抽象、多实现切换 |
| 工厂提供者 | { provide: 'CONFIG', useFactory: () => {...} } |
需要动态创建或异步初始化 |
| 值提供者 | { provide: 'API_KEY', useValue: 'xxx' } |
注入常量、配置对象、第三方库实例 |
2.3.3 依赖注入与接口反转的关系
在熔断降级场景中,我们经常需要为同一个接口提供多个实现(正常调用、熔断降级、缓存回退)。DI 容器通过自定义令牌和工厂提供者,可以在运行时动态选择注入哪个实现,从而实现接口依赖反转。
注意:不要将依赖注入与依赖反转混淆。DI 是容器技术,依赖反转是设计原则。NestJS 的 DI 容器是实现依赖反转的强力工具,但需要开发者主动设计抽象接口。
2.4 模块化结构与依赖注入关系图
下图展示了 NestJS 模块化结构中,DI 容器如何连接控制器、服务与模块之间的关系:
图示解读:AppController 不直接 new AppService(),而是通过构造函数声明依赖,由 DI 容器在运行时注入实例。ExternalModule 通过 imports 导入后,其导出的提供者也会被注册到当前模块的 DI 容器中。
2.5 实战:创建第一个业务模块
使用 CLI 生成一个完整的订单模块,体验模块化结构与 DI 的配合:
# 生成模块
nest generate module order
# 生成控制器
nest generate controller order
# 生成服务
nest generate service order
执行后,src/ 目录下会新增 order/ 文件夹,包含以下文件:
src/order/
├── order.controller.ts
├── order.module.ts
└── order.service.ts
查看自动生成的 order.module.ts:
import { Module } from '@nestjs/common';
import { OrderController } from './order.controller';
import { OrderService } from './order.service';
@Module({
controllers: [OrderController],
providers: [OrderService],
exports: [OrderService], // 导出服务,供其他模块使用
})
export class OrderModule {}
在 AppModule 中导入 OrderModule:
import { Module } from '@nestjs/common';
import { OrderModule } from './order/order.module';
@Module({
imports: [OrderModule],
})
export class AppModule {}
最佳实践:每个业务模块都应该显式声明
exports,只暴露必要的服务接口。这为后续实现熔断降级时的接口替换提供了清晰的边界。
2.6 依赖注入与模块化对熔断降级的意义
理解 DI 容器和模块化结构是掌握熔断降级设计的前提:
- 接口抽象:通过自定义令牌 + 工厂提供者,可以为同一个接口注册多个实现(正常、降级、缓存)。
- 运行时切换:DI 容器支持在运行时重新绑定提供者,实现熔断状态变化时自动切换降级逻辑。
- 模块隔离:每个模块的 DI 容器作用域独立,熔断降级策略可以按模块粒度配置,互不干扰。
- 测试友好:DI 容器让单元测试中可以轻松注入 Mock 服务,验证熔断逻辑的正确性。
注意:NestJS 默认的 DI 容器是单例作用域(Singleton),所有消费者共享同一个实例。在熔断降级场景中,这意味着熔断状态是全局共享的——这是期望的行为,但需要确保状态线程安全。