详解动态模块 Dynamic Module

67 阅读2分钟

新建 项目看看

nest new dynamic-module -p npm

新增一个模块

nest g resource bbb

此时是固定静态引入

image.png

image.png

传一些参数,动态生成模块的内容,能行吗?

使用 DynamicModule

import { DynamicModule, Module } from '@nestjs/common';
import { BbbService } from './bbb.service';
import { BbbController } from './bbb.controller';

@Module({})
export class BbbModule {
  static register(options: Record<string, any>): DynamicModule {
    return {
      module: BbbModule,
      controllers: [BbbController],
      providers: [
        {
          provide: 'CONFIG_OPTIONS',
          useValue: options,
        },
        BbbService,
      ],
      exports: [],
    };
  }
}

使用时 通过 register 方法传入参数,返回值就是模块定义

image.png

image.png

image.png

image.png

这样我们就可以在 import 一个模块的时候,传入参数,然后动态生成模块的内容。

这就是 Dynamic Module

est 约定了 3 种方法名:

  • register

  • forRoot

  • forFeature

  • register:用一次模块传一次配置,比如这次调用是 BbbModule.register({aaa: 'aaaValue',}),下一次就是 BbbModule.register({ bbb: 'bbbValue',})

  • forRoot:配置一次模块用多次,比如 XxxModule.forRoot({}) 一次,之后就一直用这个 Module,一般在 AppModule 里 import

  • forFeature:用了 forRoot 固定了整体模块,用于局部的时候,可能需要再传一些配置,比如用 forRoot 指定了数据库链接信息,再用 forFeature 指定某个模块访问哪个数据库和表

forRoot:全局配置一次,用无数次

典型使用场景

  • 数据库
  • Redis
  • 配置中心
  • Logger

👉 整个应用只需要一份配置


DatabaseModule.forRoot()

1️⃣ 定义模块

// database.module.ts
import { DynamicModule, Global, Module } from '@nestjs/common';

export const DB_CONFIG = 'DB_CONFIG';

@Global() // 可选,但非常常见
@Module({})
export class DatabaseModule {
  static forRoot(config: { host: string; port: number }): DynamicModule {
    return {
      module: DatabaseModule,
      providers: [
        {
          provide: DB_CONFIG,
          useValue: config,
        },
      ],
      exports: [DB_CONFIG],
    };
  }
}

2️⃣ 在 AppModule 里只用一次

@Module({
  imports: [
    DatabaseModule.forRoot({
      host: 'localhost',
      port: 3306,
    }),
  ],
})
export class AppModule {}

3️⃣ 任意地方直接注入

@Injectable()
export class UserService {
  constructor(
    @Inject(DB_CONFIG) private dbConfig: any,
  ) {}
}

forRoot 的一句总结

“全局只配一次,整个系统共用”

forFeature:在 forRoot 的基础上,再做局部扩展

典型组合

XxxModule.forRoot(...)     // 全局基础设施
XxxModule.forFeature(...)  // 业务模块细化配置

数据库 + 表模型

1️⃣ forRoot:建立数据库连接

// database.module.ts
@Module({})
export class DatabaseModule {
  static forRoot(config: DbConfig): DynamicModule {
    return {
      module: DatabaseModule,
      providers: [
        {
          provide: 'DB_CONNECTION',
          useValue: createConnection(config),
        },
      ],
      exports: ['DB_CONNECTION'],
    };
  }
}

2️⃣ forFeature:声明“我这个模块用哪些表”

static forFeature(entities: any[]): DynamicModule {
  return {
    module: DatabaseModule,
    providers: entities.map(entity => ({
      provide: `REPO_${entity.name}`,
      useFactory: (conn) => conn.getRepository(entity),
      inject: ['DB_CONNECTION'],
    })),
    exports: entities.map(e => `REPO_${e.name}`),
  };
}

3️⃣ 使用方式(Nest 项目常见)

@Module({
  imports: [
    DatabaseModule.forRoot({
      host: 'localhost',
      port: 3306,
    }),

    DatabaseModule.forFeature([UserEntity, OrderEntity]),
  ],
})
export class UserModule {}

4️⃣ 注入具体 Repository

@Injectable()
export class UserService {
  constructor(
    @Inject('REPO_UserEntity') private userRepo: any,
  ) {}
}

forFeature 的一句总结

“在全局能力之上,为某个业务模块补充专属配置”