前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Node.js服务端开发教程 (七):模块系统

Node.js服务端开发教程 (七):模块系统

作者头像
一斤代码
发布2019-11-29 00:47:15
1.5K0
发布2019-11-29 00:47:15
举报
文章被收录于专栏:大前端开发大前端开发

说到“模块”两字,我们脑海里肯定会浮现很多关于它好处的词汇:封装性、可复用、按需引入等等。当一个软件系统的代码规模上升到一定复杂度后,我们的确需要一些方式来条理更清晰的组织我们的代码,让代码更易阅读、团队分工协作更方便。

从一开始没有模块系统,到之后出现几大类(AMD、CMD、CommonJS、ESM)下的多种模块系统,JavaScript的代码组织和管理变得渐渐规范起来。我们可以统称这些模块系统为JavaScript模块系统,它实现了从文件层面上对变量、函数、类等各种JS内容的隔离封装,为这些内容划出了边界,并开放有限可互相沟通的入口。

NestJS框架中,在使用了JavaScript模块系统的基础上,又引入了一种特有的模块系统,就称呼它为NestJS模块系统吧,它只用于管理NestJS应用程序中的特定资源内容,声明它们在依赖注入环境下的作用域。

从之前介绍依赖注入的文章中,我们知道了NestJS中存在容器这样一个东西,那现在请把容器想象成一个集装箱,而放在这个集装箱中的一个个打包好的快递包裹就是NestJS模块,并且每个包裹里的内容只限于NestJS模块允许打包进去的东西:控制器、资源提供者。

每个NestJS应用程序其实是由模块组合而成的,它至少需要有一个模块(称为根模块)。多个模块组成一个树状结构。小型应用可能只需要一个根模块就行了,大型应用通常会由大量模块组织而成。

模块的创建

NestJS模块可以通过在一个普通的类上添加@Modue装饰器声明来创建。

代码语言:javascript
复制
import { Module } from "@nestjs/common";

@Module({
    imports: [],
    controllers: [],
    providers: [],
    exports: [],
})
export class DemoModule { }

@Module装饰器有4个配置项,它们的作用分别如下:

  • imports - 需要导入当前模块的其他模块
  • providers - 属于当前模块的资源提供者
  • controllers - 属于当前模块的路由控制器
  • exports - 当其他模块导入当前模块后,可访问到的属于当前模块的资源提供者、或由当前模块导入的其他模块

值得记住的一点是:模块默认情况对外界访问是封闭的。也就是说,一个模块在未作特别声明的情况下,其内部的资源是不能在两个模块间进行互相依赖注入的,只有本模块内部的资源才能互相注入。如果要支持跨模块注入,则需要使用上面的exports选项进行声明:

代码语言:javascript
复制
import { Module } from "@nestjs/common";
import { DemoService } from "./demo.service";

@Module({
    imports: [],
    controllers: [],
    providers: [DemoService],
    exports: [DemoService],
})
export class DemoModule { }

模块的分类:功能模块与共享模块

在实际的软件程序中,一定会存在业务类代码和辅助工具类代码。有了模块系统,我们能更好的归类划分不同职责的代码。划分的原则还是以业务和非业务功能为基础,业务上相关联的代码(包括只在该业务中所使用的工具代码)尽量组织在同一个模块中;而和业务无关的、可被其他模块通用的代码,可以按功能分类组织在一个或多个模块之中。

模块的重组

一个模块可以通过imports导入其他模块,也可以通过exports再次导出这些导入的模块。这样做的目的是:可以实现将各种小粒度的模块排列组合成各种稍大粒度的模块,按照实际需要选择使用稍大粒度的模块,而不是总导入数量较多的小粒度模块。

代码语言:javascript
复制
@Module({
  imports: [HelperAModule, HelperBModule],
  exports: [HelperAModule, HelperBModule],
})
export class HelperModule {}

模块的依赖注入

模块类本身也可以进行依赖注入,让其他资源注入到模块类中。如下所示:

代码语言:javascript
复制
import { Module } from '@nestjs/common';
import { DemoService } from './demo.service';

@Module({
  imports: [],
  controllers: [],
  providers: [DemoService],
  exports: [DemoService],
})
export class DemoModule {
  constructor(private readonly demoService: DemoService) {
    console.log(demoService);
  }
}

模块的全局化

假设你有一些模块(比如数据库连接模块、Redis缓存模块、一些公用工具模块等),它们几乎在你所有的其他模块中都会被用到,那么你需要在所有这些用到它们的模块中都导入它们,这会让你的代码看起来有那么点啰嗦。

为了解决这个问题,NestJS提供了将模块声明成全局作用域的方式,即使用@Global装饰器:

代码语言:javascript
复制
import { Module, Global } from '@nestjs/common';
import { DemoService } from './demo.service';

@Global()
@Module({
  imports: [],
  controllers: [],
  providers: [DemoService],
  exports: [DemoService],
})
export class DemoModule {}

这样一来,需要使用到这个DemoModule中资源的其他模块,就不需要通过imports来导入它就能使用了。

动态模块

有时候,为了一个模块更好的被复用,我们希望它可以通过配置参数的形式来提供具有差异化的功能。比如一个数据库连接模块,你肯定不希望它总是连接的同一个服务器上的数据库,或者用户名和密码总是固定的。所以,像这样的模块,我们希望它实例化的时候是可接受额外参数,或者可以自定义一些中间过程。为了实现这样的功能,NestJS模块提供了可动态生成模块实例的方式,来看下面的示例,它将通过一个参数来让模块中的资源提供者产生变化:

代码语言:javascript
复制
import { Module, DynamicModule } from '@nestjs/common';
import { DemoService } from './demo.service';

@Module({})
export class DemoModule {

    static register(options): DynamicModule {
        // Mockup对象
        const mockDemoService = {
            test() {
                return 'hello,world';
            }
        };

        const definition = {
            module: DemoModule,
            imports: [],
            controllers: [],
            providers: [
                // 根据配置参数中的isDebug值,来决定使用真正的DemoService
                // 作为资源提供者,还是用mockup对象
                options.isDebug ? {
                    provide: DemoService,
                    useValue: mockDemoService
                } : DemoService
            ],
            exports: [DemoService],
        };

        return definition;
    }
}

我们将本来模块类上的@Module装饰器的参数选项都移除,然后在DemoModule模块类中定义一个静态方法register,该方法接受一个options参数(其实这里的方法名和参数名、参数个数都可以随你自己的需要来定,没有什么限制),且该方法返回的类型为DynamicModule。然后该方法内部就是具体去拼装一个和@Module装饰器参数选项类似的动态模块信息了。

实现上述的动态模块后,在使用它的地方就可以这样来写:

代码语言:javascript
复制
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { DemoModule } from './demo.module';

@Module({
  // 调用模块中的静态方法获取动态模块
  imports: [DemoModule.register({ isDebug: false })],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule { }

是不是非常容易理解?

总结

使用好NestJS的模块系统,并结合依赖注入,可以更好的去管理你的应用程序代码。在设计系统时,请一定要事先规划一下你的模块,以及互相间的依赖关系,可以让你在开发实现时事半功倍。

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 模块的创建
  • 模块的分类:功能模块与共享模块
  • 模块的重组
  • 模块的依赖注入
  • 模块的全局化
  • 动态模块
  • 总结
相关产品与服务
容器服务
腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档