模块(Module)的概念
NestJS 模块是应用程序的基本组织单元,每个模块都是一个使用 @Module() 装饰器装饰的类。模块将相关的组件(控制器、提供者等)组织在一起,形成内聚的功能单元。
模块的基本结构
typescriptimport { Module } from '@nestjs/common'; import { UsersController } from './users.controller'; import { UsersService } from './users.service'; @Module({ imports: [], // 导入其他模块 controllers: [UsersController], // 声明控制器 providers: [UsersService], // 声明提供者 exports: [UsersService], // 导出提供者供其他模块使用 }) export class UsersModule {}
模块装饰器的属性
- imports: 导入其他模块,使其提供者可用
- controllers: 声明属于该模块的控制器
- providers: 声明属于该模块的提供者
- exports: 导出提供者,使其对其他模块可用
- providers 和 exports 的区别: providers 只在当前模块内可用,exports 可以被其他模块使用
模块类型
- 根模块(Root Module):应用程序的入口模块
- 功能模块(Feature Module):封装特定功能的模块
- 共享模块(Shared Module):导出提供者供多个模块使用
- 全局模块(Global Module):使用
@Global()装饰器,自动导入到所有模块
依赖注入(Dependency Injection)
依赖注入是 NestJS 的核心设计模式,它实现了控制反转(IoC),使代码更加松耦合、可测试和可维护。
依赖注入的工作原理
- 提供者注册:在模块中注册提供者
- 依赖声明:在构造函数中声明依赖
- 自动解析:NestJS 自动解析并注入依赖
基本示例
typescript@Injectable() export class UsersService { constructor(private readonly userRepository: UserRepository) {} async findAll() { return this.userRepository.findAll(); } } @Controller('users') export class UsersController { constructor(private readonly usersService: UsersService) {} @Get() findAll() { return this.usersService.findAll(); } }
依赖注入的优势
- 松耦合:组件之间通过接口而非具体实现交互
- 可测试性:易于在测试中替换依赖
- 可维护性:依赖关系清晰,便于修改
- 可重用性:提供者可以在多个地方使用
作用域(Scopes)
NestJS 提供三种依赖注入作用域:
1. 默认作用域(Singleton)
typescript@Injectable() export class UsersService {}
- 整个应用程序只有一个实例
- 适用于无状态的服务
2. 请求作用域(REQUEST)
typescript@Injectable({ scope: Scope.REQUEST }) export class UsersService {}
- 每个请求创建一个新实例
- 适用于需要请求特定状态的服务
3. 瞬时作用域(TRANSIENT)
typescript@Injectable({ scope: Scope.TRANSIENT }) export class UsersService {}
- 每次注入都创建新实例
- 适用于需要独立实例的场景
循环依赖
循环依赖是指两个或多个模块相互依赖。NestJS 提供了几种解决方案:
1. 使用 forwardRef()
typescript@Module({ imports: [forwardRef(() => BModule)], }) export class AModule {} @Module({ imports: [forwardRef(() => AModule)], }) export class BModule {}
2. 重构代码结构
- 将共享功能提取到单独的模块
- 使用事件驱动架构替代直接依赖
最佳实践
- 模块化设计:按功能领域划分模块
- 单一职责:每个模块只负责一个功能领域
- 依赖最小化:避免不必要的依赖
- 使用接口:通过接口定义依赖契约
- 避免循环依赖:设计时避免模块间的循环依赖
- 合理使用作用域:根据需求选择合适的作用域
- 导出必要的提供者:只导出需要被其他模块使用的提供者
总结
NestJS 的模块和依赖注入系统是其架构的核心,它们提供了:
- 清晰的代码组织结构
- 松耦合的组件设计
- 高度的可测试性
- 良好的可维护性
掌握模块和依赖注入是使用 NestJS 构建高质量应用程序的基础,它们使开发者能够构建可扩展、可维护的企业级应用。