前面有介绍过 Module 的一些基本使用方式,然而有一项非常强大的功能没有被提及,就是 动态模组(Dynamic Module),它可以用很简单的方式去客制化 Provider 的内容,使该 Module 的 Provider 动态化,什麽意思呢?简单来说,就是我们希望这个 Module 是可以透过外部传入参数去设置 Provider 的内容,与一般 静态模组(Static Module) 不同的地方在於,静态模组建立後 Provider 即建立完毕,若要更改 Provider 相关配置则要变动这个 Module 内部的程序码;动态模组则是将可能会变动的部分 参数化,让使用者在使用此 Module 时,可以透过其提供的 静态方法 来带入参数,让 Provider 接受该参数并建立 Module。
用生活中的例子来说明的话,静态模组就像一个专用遥控器,在没有去改写内部的规则之前,它只能针对特定设备做控制;动态模组就像一个万用遥控器,同样是控制设备,但只需要根据特定的操作就能去控制不同的设备。
动态模组是很常使用的功能,其中,最常遇到的情境就是环境变数管理,设计一个 Module 专门处理环境变数,这样的情境非常适合使用动态模组来处理,原因是管理环境变数的逻辑通常是不变的,会变的部分仅仅是读取环境变数的档案路径等,透过动态模组的机制成功将其抽离成共用元件,降低耦合度。
注意:关於环境变数的介绍会在下篇做更详细的说明。
这篇我们会运用动态模组与 dotenv 来实作一套简单的环境变数管理模组,名称定为 ConfigurationModule
。
注意:
dotenv
是一套用於管理环境变数的套件,详细内容可以参考官方文件。
目标是让 ConfigurationModule
提供一个静态方法 forRoot
,它可以接受一个包含 key
值为 path
的物件参数,path
即 .env
档的相对路径,透过 forRoot
将参数带给 ConfigurationService
来处理 .env
的档案并管理解析出来的变数。首先,透过 npm
安装 dotenv
:
$ npm install dotenv --save
透过 CLI 产生 ConfigurationModule
与 ConfigurationService
:
$ nest generate module common/configuration
$ nest generate service common/configuration
接着打开 configuration.module.ts
,替 ConfigurationModule
添加一个 forRoot
静态方法,回传的值即为 DynamicModule
,而 DynamicModule
其实就是一个物件,与 @Module
装饰器内的参数大致相同,不同的是必须要带上 module
参数,其值为 ConfigurationModule
本身,另外,还有 global
参数可以使产生出来的 Module 变成全域:
import { DynamicModule, Module } from '@nestjs/common';
import { ConfigurationService } from './configuration.service';
@Module({})
export class ConfigurationModule {
static forRoot(): DynamicModule {
return {
providers: [
ConfigurationService
],
module: ConfigurationModule,
global: true
};
}
}
注意:静态方法可以自行设计,但回传值必须为同步或非同步
DynamicModule
,名称通常会使用forRoot
或register
。
从上方程序码可以看出 @Module
的参数净空了,这是为什麽呢?因为我们只使用动态模组,所以没有特别设计静态模组的部分,但如果要设计也是可以的。
接下来要在 forRoot
设计包含 key
值为 path
的物件参数,并将 path
取出,运用 Value Provider 的方式将该值记录下来。先在 configuration
资料夹下新增 constants
资料夹,并在里面建立 token.const.ts
来管理 token
:
export const ENV_PATH = 'ENV_PATH';
调整 configuration.module.ts
:
import { DynamicModule, Module } from '@nestjs/common';
import { ConfigurationService } from './configuration.service';
import { ENV_PATH } from './constants/token.const';
@Module({})
export class ConfigurationModule {
static forRoot(options: { path: string }): DynamicModule {
return {
providers: [
{
provide: ENV_PATH,
useValue: options.path
},
ConfigurationService
],
exports: [
ConfigurationService
],
module: ConfigurationModule,
global: true
};
}
}
最後就是设计 ConfigurationService
的内容了,在 constructor
注入刚才设计的环境变数路径 ENV_PATH
,接着设计 setEnvironment
去读取并解析 .env
档,然後写入 config
属性中,最後设计一个 get(key: string)
的方法来提取要用的环境变数:
import { Inject, Injectable } from '@nestjs/common';
import * as fs from 'fs';
import * as path from 'path';
import * as dotenv from 'dotenv';
import { ENV_PATH } from './constants/token.const';
@Injectable()
export class ConfigurationService {
private config: any;
constructor(
@Inject(ENV_PATH) private readonly path: string
) {
this.setEnvironment();
}
public get(key: string): string {
return this.config[key];
}
private setEnvironment(): void {
const filePath = path.resolve(__dirname, '../../', this.path);
this.config = dotenv.parse(fs.readFileSync(filePath));
}
}
设计完 ConfigurationModule
以後,先在专案路径下新增 development.env
档,并设定里面的内容:
USERNAME=HAO
注意:是新增在专案路径下,与
package.json
同层级,非src
。
接着,调整 app.module.ts
的内容:
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { ConfigurationModule } from './common/configuration/configuration.module';
@Module({
imports: [
ConfigurationModule.forRoot({
path: `../${process.env.NODE_ENV || 'development'}.env`
})
],
controllers: [
AppController
],
providers: [
AppService
]
})
export class AppModule {
}
调整 app.controller.ts
的内容,在 constructor
注入 ConfigurationService
,并改写 getHello
回传值:
import { Controller, Get } from '@nestjs/common';
import { ConfigurationService } from './common/configuration/configuration.service';
@Controller()
export class AppController {
constructor(
private readonly configService: ConfigurationService
) {
}
@Get()
getHello() {
return { username: this.configService.get('USERNAME') };
}
}
透过浏览器查看 http://localhost:3000,会得到 USERNAME
的值:
Dynamic Module 是非常好用且实用的功能,经常运用在资料库、环境变数管理等功能,不过需要对 Nest 的依赖注入机制有一定程度的了解,在基础稳固之後学习上比较不会有问题。这里附上今天的懒人包:
DynamicModule
型别的物件。DynamicModule
必须包含 module
参数。forRoot
或 register
。
>>: [前端暴龙机,Vue2.x 进化 Vue3 ] Day21. 『小专题◕ᴥ◕』 Vue 旅游小帮手(二)
834. Sum of Distances in Tree https://leetcode.com...
前言 大家好,我是刚从硕士班毕业不到一年的社会新鲜人,目前担任小小的AI工程师。 兴趣是资料分析和深...
spork 为加速测试用套件,透过启用 DRB server 载入环境让你在执行测试时只需要载入一次...
第一次参加铁人赛,原本以为超前部署,开赛前两个星期就开始准备文章存档 本以为一定妥当的啦,没想到後面...
前言 最近这两年受到疫情的冲击,尤其是从今年五月中开始疫情第三级警戒,许多企业开始裁员,失业率创近期...