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 依赖注入的工作流程

  1. 注册提供者:在模块的 providers 数组中声明类或令牌。
  2. 声明依赖:在构造函数中通过类型注解或 @Inject() 声明需要注入的依赖。
  3. 容器解析:NestJS 运行时扫描构造函数参数,从容器中查找匹配的提供者实例。
  4. 注入实例:容器将解析后的实例注入到构造函数中,完成对象组装。

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 容器如何连接控制器、服务与模块之间的关系:

NestJS 模块化结构与依赖注入关系 NestJS 模块化结构与依赖注入关系 AppModule 边界 AppController @Controller('/') AppService @Injectable() ExternalModule imports 导入 NestJS DI 容器 AppService 实例 ExternalService ConfigProvider ...其他提供者 注入 导入 控制器通过 DI 容器获取服务实例,模块通过 imports 导入外部模块的提供者
图示解读: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),所有消费者共享同一个实例。在熔断降级场景中,这意味着熔断状态是全局共享的——这是期望的行为,但需要确保状态线程安全。