Nest提供了一套结构化和模块化的方式来管理应用程序中不同的部分,通过@Module装饰器来声明模块。
从上图中看到,所有应用都会有一个根模块,Nest会从根模块开始收集各个模块之间的依赖关系,形成依赖关系树,在应用初始化时根据依赖关系树实例化不同的模块对象。
创建模块
创建一个nest-module项目
arduino
nest n nest-module -p pnpm
默认根模块为AppModule,类装饰器@Module的controllers为注入该模块的控制器集,providers为注入该模块的提供者,它们将在该模块被共享。
imports正是用于导入应用中的其他模块,默认为空,以创建新的User和Order模块为例:
sql
nest g resource User --no-spec
nest g resource Order --no-spec
再来看AppModule,UserModule和OrderModule被自动导入到根模块中,成为AppModule的子模块
共享模块
既然有imports,想必也有exports,假设有一个需求,Order模块需要依赖User模块的UserService,那么可以将UserService加入到exports中,使之成为共享服务。此时,Order模块只需导入UserModule即可访问UserService,通过案例来演示一遍:
在User模块中导出User服务
接着在Order模块中导入User模块,此时在Order模块的任何地方,都可以共享User服务
在Order控制器定义路由函数getOrder,再调用orderService中的getOrder方法
在Order服务中通过属性注入User服务依赖,同时调用User服务的getUserHello方法,最终返回一个问候语字符串!
除了属性注入 依赖之外,还可以使用构造函数注入
浏览器中输入访问http://localhost:3000/order,成功返回内容到页面
全局模块
如果有模块被多个地方引用,为了方面,可以使用@Global把它声明为全局模块,这样便可直接注入exports出来的provider,无须再逐个imports
注意使用全局模块前确保你的模块为全局使用,避免导致模块之间的耦合性增加。
动态模块
以上的模块都属于静态模块的绑定和使用,Nest中还提供了动态加载模块的能力,使得应用可以在运行时创建模块,通常用于动态读取配置或根据权限判断来加载模块。基本用法如下:
创建动态模块
typescript
// dynamic.module.ts
import { Module, DynamicModule } from '@nestjs/common';
@Module({})
export class DynamicModule {
static forFeature(entities: Function[]): DynamicModule {
// 在此方法中根据需要创建模块
return {
module: DynamicModule,
providers: [],
exports: [],
};
}
}
在需要使用动态模块的地方,例如某个服务或控制器中,使用 forFeature
方法来动态加载模块
typescript
// some.service.ts
import { Injectable } from '@nestjs/common';
import { DynamicModule } from './dynamic.module';
@Injectable()
export class SomeService {
constructor(private readonly dynamicModule: DynamicModule) {}
// 在某个方法中动态加载模块
loadModule(entities: Function[]) {
return this.dynamicModule.forFeature(entities);
}
}
需要动态加载模块时,调用 loadModule
方法即可加载相应模块。
除此之外,还有register
和forRoot
方法可用于加载动态模块,register
方法通常与外部模块或第三方库集成时使用,它将外部模块动态加载到 Nest.js 模块中。
typescript
import { Module } from '@nestjs/common';
import * as someExternalLibrary from 'some-external-library';
@Module({})
export class DynamicModule {
static register(options: SomeOptions) {
// 在此方法中注册外部模块
return {
module: DynamicModule,
providers: [],
exports: [],
};
}
}
而forRoot
在 Nes中常用于注册根模块。例如用于配置根模块的全局服务或中间件等。
typescript
import { Module } from '@nestjs/common';
@Module({})
export class CoreModule {
static forRoot(options: SomeOptions) {
// 在此方法中配置根模块的全局服务或特性
return {
module: CoreModule,
providers: [],
exports: [],
};
}
}
总结
Nest提供了一套模块化的解决方案,通过@Module来定义模块,模块之间可以通过imports和exports来实现交互和通信,@Global声明为全局模块,但是使用的是需要一定的抽象能力。
还提供了动态加载模块的能力,可以在运行时创建模块,通常用于动态读取配置,如链接数据库配置等,会频繁使用。
Nest的模块就这些概念,掌握之后就明白如何组织功能代码了。