Nest基础(二):Module

Nest基础(一):Controller & Provider 中我们介绍了用来接收请求的Controller与提供服务的Providers,二者由于都需要依赖注入,故都在@Module中留下了自己的名号。那么本篇我们来看看@Module是何方神圣。老规矩,文章结构在此:


  • 梳理

    • providers vs imports

  • 基本使用

  • 共享模块

  • Module 层级整理

  • Module 注入

  • 全局模块

  • 动态模块

  • 总结


梳理

模块是具有 @Module() 装饰器的类。@Module() 装饰器提供了元数据,Nest 用它来组织应用程序结构。

@Module() 装饰器描述模块属性的对象

  • imports: 一个包含其他模块的数组,允许在当前模块中使用其他模块中导出的 providers。这些模块中的提供者将被添加到当前模块的提供者列表中,并且可以在当前模块中使用。
  • controllers: 一个包含控制器类的数组,这些控制器类由当前模块负责创建和管理。控制器类必须使用 @Controller() 装饰器进行注解。
  • providers: 一个包含服务、提供者或工厂函数的数组,这些服务由当前模块负责创建和管理。提供者可以是任何可注入的对象,如服务类、工厂函数或提供者对象
  • exports: 一个包含要导出的提供者或模块的数组,允许其他模块访问当前模块中定义的提供者

providers vs imports

这两个属性感觉很像,都是暴露出来注入给别人的。但本质上区别如下:

  • imports: 用于导入其他模块中的服务
  • providers: 用于注册当前模块中的服务或提供者。

举个栗子,假设有一个UserModule模块和一个LoggerService服务。

如果你想在当前模块中使用 UserService,你应该将 UserModule 添加到 imports 中,以便在当前模块中访问 UserService

@Module({
  imports: [UserModule],
})
export class AppModule {}

如果你想在当前模块中注册 LoggerService,你应该将它添加到 providers 中,这样它就会成为当前模块的提供者。

@Module({
  providers: [LoggerService],
})
export class AppModule {}

在这个例子中,UserModule 是另一个模块,通过将其添加到 imports 中,我们可以在当前模块中使用 UserService。而 LoggerService 是当前模块的服务,通过将其添加到 providers 中,我们将它注册为当前模块的提供者。

模块是具有 @Module() 装饰器的提供者。@Module() 装饰器提供了元数据,Nest 用它来组织应用程序结构。
@Module() 装饰器描述模块属性的对象

  • imports: 一个包含其他模块的数组,允许在当前模块中使用其他模块中导出的 providers。这些模块中的提供者将被添加到当前模块的提供者列表中,并且可以在当前模块中使用。
  • controllers: 一个包含控制器提供者的数组,这些控制器提供者由当前模块负责创建和管理。控制器提供者供者供者必须使用 @Controller() 装饰器进行注解。
  • providers: 一个包含服务、提供者或工厂函数的数组,这些服务由当前模块负责创建和管理。提供者可以是任何可注入的对象,如服务提供者供者供者供者、工厂函数或提供者对象
  • exports: 一个包含要导出的提供者或模块的数组,允许其他模块访问当前模块中定义的提供者

应用模块(根模块)由多个模块构成,结构示意图如下:
Nest基础(二):Module


基本使用

假设我们现在需要对“游戏”进行管理,写了两个类,一个service用来执行业务逻辑和一个controller来管理请求。
为了使得service在controller中能够实现依赖注入,我们在games/games.module.ts中注册这两者:

import { Module } from '@nestjs/common';
import { GamesController } from './games.controller';
import { GamesService } from './games.service';

@Module({
  controllers: [GamesController],
  providers: [GamesService],
})
  export class GamesModule {}

有了自己的模块还不行,还得让应用知道你有这个模块,所以需要在app.module.ts中注册 GamesModule:

import { Module } from '@nestjs/common';
import { GamesModule } from './games/games.module';

@Module({
  imports: [GamesModule],
})
export class ApplicationModule {}


共享模块

在 Nest 中,默认情况下,模块是单例,也就意味着多个模块之间可以共享同一个提供者实例
Nest基础(二):Module
上图中的Users ModuleOrders Module等模块中,都可能含有ServiceController等类,通过在自身 Module 中进行注册导出,其他 Module 中仅需导入对应 Module 即可使用。
举个栗子。

  1. 自身模块中进行注册导出:
import { Module } from '@nestjs/common';
import { GamesController } from './games.controller';
import { GamesService } from './games.service';

@Module({
  controllers: [GamesController],
  providers: [GamesService],
  exports: [GamesService]
})
  export class GamesModule {}
  1. 在对应要使用的类所属 Module 中进行引入:
import { Module } from '@nestjs/common';
import { GamesModule } from '../games/games.module';

@Module({
  imports: [GamesModule],
  providers: [OtherService],
})
export class OtherModule {}
  1. 在对应要使用的类中进行注入:
import { Injectable } from '@nestjs/common';
import { GamesService } from '../games/games.service';

@Injectable()
export class OtherService {
  constructor(private readonly gamesService: GamesService) {}

  // 使用 gamesService 提供的功能
}


Module 层级整理

有时候,在编写js项目的时候我们会在一个目录下写很多类,然后在index.js中将该文件夹下所有文件导入再导出,这样我们只需引入目录即可使用这些文件了。如utils目录下有map.jstransformer.js等,经过utils/index.js的导出,其他文件仅需import { map } from '../utils'即可进入导入。

同样的,在nest中,我们可以通过对 Module 层级的梳理来实现。
如现在对现有的三个模块封装到CoreModule中:

import { Module } from '@nestjs/common';
import { CommonModule } from './common/common.module'// 假设有这个模块
import { GamesModule } from './games/games.module'// 假设有这个模块
import { PlatformsModule } from './platforms/platforms.module'// 假设有这个模块

@Module({
  imports: [CommonModule, GamesModule, PlatformsModule],
  exports: [CommonModule, GamesModule, PlatformsModule],
})
export class CoreModule {}

想要使用三个模块中的功能时,就只用导入一个模块了:

import { Module } from '@nestjs/common';
import { CoreModule } from './core/core.module';
import { OtherController } from './other.controller';

@Module({
  // 导入 CoreModule,这会包括 CommonModule、GamesModule、PlatformsModule 中的功能
  controllers: [OtherController],
  imports: [CoreModule], 
})
export class OtherModule {}


Module 注入

根据它的用法,我们可以发现,Module更像是一个包装袋,其具有的抽象能力,可以帮助我们暴露和引入其他抽象模块的方法。那么,可以将其他类注入到 Module 中吗?答案是可以的,其他提供者可以注入到 Module 中,但 Module 不能注入到提供者中(避免循环依赖)

我们会将提供者注入到 Module 中,以提供便于配置相关等目的。举几个栗子。

应用场景1:配置与定制化

import { Module } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { LoggerService } from './logger.service';

@Module({
  providers: [LoggerService],
})
export class MyModule {
  constructor(
    private readonly configService: ConfigService,
    private readonly loggerService: LoggerService,
  
) {
    // 基于配置设置日志等级
    this.loggerService.setLevel(this.configService.get('LOG_LEVEL'));
  }
}

应用场景2:访问共享模块

import { Module } from '@nestjs/common';
import { AuthService } from '../auth/auth.service';

@Module({
  providers: [MyService],
})
export class MyModule {
  constructor(private readonly authService: AuthService) {
    // 在模块内执行授权
    this.authService.authorizeUser();
  }
}

应用场景3:访问数据库

import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { CatRepository } from './cat.repository';

@Module({
  imports: [TypeOrmModule.forFeature([CatRepository])],
})
export class CatsModule {
  constructor(private readonly catRepository: CatRepository) {
    this.catRepository.findOne();
  }
}

这三个例子大差不差,基本上跟注入到其他类中是一样的。


全局模块

全局模块是指在应用程序的所有模块中都可用的模块。全局模块通常用于提供全局范围内的功能,例如配置、数据库、工具类、日志记录或错误处理等。

import { Module, Global } from '@nestjs/common';
import { CatsController } from './cats.controller';
import { CatsService } from './cats.service';

@Global()
@Module({
  controllers: [CatsController],
  providers: [CatsService],
  exports: [CatsService],
})
export class CatsModule {}


@Global 装饰器使模块成为全局作用域。全局模块应该只注册一次,最好由根或核心模块注册。在上面的例子中,CatsService 组件将无处不在,而想要使用 CatsService 的模块则不需要在 imports 数组中导入 CatsModule。

但是官网也提示了**使一切全局化并不是一个好的解决方案。**


动态模块

nest中的动态模块使您可以轻松创建可自定义的模块,这些模块可以动态注册和配置提供程序。
动态配置数据的模块为例:

import { Module, DynamicModule } from '@nestjs/common'// 导入模块和动态模块所需的类型
import { createDatabaseProviders } from './database.providers'// 导入用于创建数据库提供者的函数
import { Connection } from './connection.provider'// 导入数据库连接提供者

@Module({
  providers: [Connection], // 将 Connection 提供者注册到 DatabaseModule 中
})
export class DatabaseModule {
  /**
   * 静态方法,用于配置和返回动态模块
   * @param entities 要与模块关联的实体列表
   * @param options 配置选项(可选)
   * @returns 配置好的动态模块
   */

  static forRoot(entities = [], options?): DynamicModule {
    // 使用 createDatabaseProviders 函数创建数据库提供者
    const providers = createDatabaseProviders(options, entities);

    // 定义并返回动态模块
    return {
      module: DatabaseModule, // 指定模块的名称
      providers: providers, // 提供模块中可用的提供者
      exports: providers, // 将提供者导出,使其在导入该模块的其他模块中可用
    };
  }
}

forRoot() 可以同步或异步(Promise)返回动态模块。

  • **@Module({ providers: [Connection] })**: 将 Connection 提供者注册到 DatabaseModule 中,使其在该模块的范围内可用。
  • **static forRoot(entities = [], options?)**: 静态方法,用于配置和返回动态模块。
    • **entities**: 要与模块关联的实体列表,用于创建数据库提供者。
    • **options**: 可选的配置选项,用于传递额外的配置信息。
  • **const providers = createDatabaseProviders(options, entities);**: 调用 createDatabaseProviders 函数来创建数据库提供者,这些提供者通常会处理数据库连接、实体管理等功能。
  • **return { ... };**: 返回一个对象,该对象定义了动态模块:
    • **module: DatabaseModule**: 指定模块的名称。
    • **providers: providers**: 将创建的提供者列表添加到模块中。
    • **exports: providers**: 将提供者导出,使其在导入该模块的其他模块中可用。

使用时:

@Module({
  imports: [
    DatabaseModule.forRoot([User, Product], {
      // 配置选项
    }),
  ],
})
export class AppModule {}

动态模块具体会在后面详细研究,此处先按下不表。


总结

现将不同的使用场景下的步骤进行总结:

  • 使用依赖注入本模块其他提供者时:
    • ① 在 Module 中注册
    • ② 在对应提供者中注入
  • 给其他模块使用某些提供者时:
    • ① 在 Module 中注册,并加入 exports 列表中
    • ② 在对应提供者所属 Module 中使用 imports 注册
    • ③ 在对应提供者中注入

原文始发于微信公众号(anywareAI):Nest基础(二):Module

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/202249.html

(0)
小半的头像小半

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!