了解Node.js Nestjs框架的模块机制,聊聊实现原理

本篇文章带大家了解一下Node 后端框架Nest.js , 介绍一下Nestjs模块机制的概念和实现原理 , 希望对大家有所帮助!

了解Node.js Nestjs框架的模块机制,聊聊实现原理

文章插图

Nest 提供了模块机制 , 通过在模块装饰器中定义提供者、导入、导出和提供者构造函数便完成了依赖注入 , 通过模块树组织整个应用程序的开发 。 按照框架本身的约定直接撸一个应用程序 , 是完全没有问题的 。 可是 , 于我而言对于框架宣称的依赖注入、控制反转、模块、提供者、元数据、相关装饰器等等 , 觉得缺乏一个更清晰系统的认识 。
    为什么需要控制反转?什么是依赖注入?装饰器做了啥?模块 (@Module) 中的提供者(providers) , 导入(imports)、导出(exports)是什么实现原理?
好像能够理解 , 能够意会 , 但是让我自己从头说清楚 , 我说不清楚 。 于是进行了一番探索 , 便有了这篇文章 。 从现在起 , 我们从新出发 , 进入正文 。
1 两个阶段1.1 Express、Koa
一个语言和其技术社区的发展过程 , 一定是从底层功能逐渐往上丰富发展的 , 就像是树根慢慢生长为树枝再长满树叶的过程 。 在较早 , Nodejs 出现了 Express 和 Koa 这样的基本 Web 服务框架 。 能够提供一个非常基础的服务能力 。 基于这样的框架 , 大量的中间件、插件开始在社区诞生 , 为框架提供更加丰富的服务 。 我们需要自己去组织应用依赖 , 搭建应用脚手架 , 灵活又繁琐 , 也具有一定工作量 。
发展到后面 , 一些生产更高效、规则更统一的框架便诞生了 , 开启了一个更新的阶段 。
1.2 EggJs、Nestjs
为了更加适应快速生产应用 , 统一规范 , 开箱即用 , 便发展出了 EggJs、NestJs、Midway等框架 。 此类框架 , 通过实现底层生命周期 , 将一个应用的实现抽象为一个通用可扩展的过程 , 我们只需要按照框架提供的配置方式 , 便可以更简单的实现应用程序 。 框架实现了程序的过程控制 , 而我们只需要在合适位置组装我们的零件就行 , 这看起来更像是流水线工作 , 每个流程被分割的很清楚 , 也省去了很多实现成本 。
1.3 小结
上面的两个阶段只是一个铺垫 , 我们可以大致了解到 , 框架的升级是提高了生产效率 , 而要实现框架的升级 , 就会引入一些设计思路和模式 , Nest 中就出现了控制反转、依赖注入、元编程的概念 , 下面我们来聊聊 。
2 控制反转和依赖注入2.1 依赖注入
一个应用程序实际就是非常多的抽象类 , 通过互相调用实现应用的所有功能 。 随着应用代码和功能复杂度的增加 , 项目一定会越来越难以维护 , 因为类越来越多 , 相互之间的关系越来越复杂 。
举个例子 , 假如我们使用 Koa 开发我们的应用 , Koa 本身主要实现了一套基础的 Web 服务能力 , 我们在实现应用的过程中 , 会定义很多类 , 这些类的实例化方式、相互依赖关系 , 都会由我们在代码逻辑自由组织和控制 。 每个类的实例化都是由我们手动 new , 并且我们可以控制某个类是只实例化一次然后被共享 , 还是每次都实例化 。 下面的 B 类依赖 A , 每次实例化 B 的时候 , A 都会被实例化一次 , 所以对于每个实例 B 来说 , A 是不被共享的实例 。
class A{}// Bclass B{ contructor(){ this.a = new A(); }}下面的 C 是获取的外部实例 , 所以多个 C 实例是共享的 app.a 这个实例 。
class A{}// Cconst app = {};app.a = new A();class C{ contructor(){ this.a = app.a; }}下面的 D 是通过构造函数参数传入 , 可以每次传入一个非共享实例 , 也可以传入共享的 app.a 这个实例(D 和 F 共享 app.a) , 并且由于现在是参数的方式传入 , 我也可以传入一个 X 类实例 。
class A{}class X{}// Dconst app = {};app.a = new A();class D{ contructor(a){ this.a = a; }}class F{ contructor(a){ this.a = a; }}new D(app.a)new F(app.a)new D(new X())这种方式就是依赖注入 , 把 B 所依赖的 A , 通过传值的方式注入到 B 中 。 通过构造函数注入(传值)只是一种实现方式 , 也可以通过实现 set 方法调用传入 , 或者是其他任何方式 , 只要能把外部的一个依赖 , 传入到内部就行 。 其实就这么简单 。
class A{}// Dclass D{ setDep(a){ this.a = a; }}const d = new D()d.setDep(new A())2.2 All in 依赖注入?
随着迭代进行 , 出现了 B 根据不同的前置条件依赖会发生变化 。 比如 , 前置条件一 this.a 需要传入 A 的实例 , 前置条件二this.a需要传入 X 的实例 。 这个时候 , 我们就会开始做实际的抽象了 。 我们就会改造成上面 D 这样依赖注入的方式 。

推荐阅读