Maison > interface Web > js tutoriel > Comprendre le mécanisme des modules du framework Node.js Nestjs et parler des principes de mise en œuvre

Comprendre le mécanisme des modules du framework Node.js Nestjs et parler des principes de mise en œuvre

青灯夜游
Libérer: 2022-04-25 20:59:03
avant
3277 Les gens l'ont consulté

Cet article vous amènera à comprendre le framework back-end Node Nest.js et à présenter le concept et le principe de mise en œuvre du mécanisme du module Nestjs. J'espère qu'il sera utile à tout le monde !

Comprendre le mécanisme des modules du framework Node.js Nestjs et parler des principes de mise en œuvre

Nest fournit un mécanisme de module. L'injection de dépendances est complétée par la définition des fournisseurs, des importations, des exportations et des constructeurs de fournisseurs dans le décorateur de module, et le développement de l'ensemble de l'application est organisé via l'arborescence des modules. Il n’y a absolument aucun problème à lancer directement une application selon les conventions du framework lui-même. Cependant, pour moi, j'estime qu'il me manque une compréhension plus claire et systématique de l'injection de dépendances, de l'inversion de contrôle, des modules, des fournisseurs, des métadonnées, des décorateurs associés, etc. déclarés par le framework.

  • Pourquoi avez-vous besoin d'une inversion de contrôle ?
  • Qu’est-ce que l’injection de dépendance ?
  • Que fait le décorateur ?
  • Quels sont les principes de mise en œuvre des fournisseurs, des importations et des exportations dans le module (@Module) ?

Il semble que je puisse le comprendre et l'apprécier, mais laissez-moi l'expliquer clairement dès le début, je ne peux pas l'expliquer clairement. J'ai donc fait quelques recherches et j'ai rédigé cet article. A partir de maintenant, nous partons de zéro et entrons dans le texte principal.

1 Deux étapes

1.1 Express, Koa

Le processus de développement d'un langage et de sa communauté technique doit progressivement s'enrichir et se développer depuis les fonctions inférieures vers le haut, tout comme les racines d'un arbre qui se transforment lentement en branches. processus de repousse des feuilles. Auparavant, Nodejs avait émergé avec des frameworks de services Web de base comme Express et Koa. Capable de fournir une capacité de service très basique. Sur la base d'un tel framework, un grand nombre de middlewares et de plug-ins ont commencé à naître dans la communauté, fournissant des services plus riches pour le framework. Nous devons organiser les dépendances des applications et créer nous-mêmes un échafaudage d'applications, qui est flexible et lourd, et nécessite également une certaine charge de travail.

Au fur et à mesure de son développement ultérieur, certains cadres avec une production plus efficace et des règles plus unifiées sont nés, ouvrant la voie à une nouvelle étape.

1.2 EggJs, Nestjs

Afin d'être plus adaptables aux applications de production rapide, d'unifier les normes et de les rendre disponibles immédiatement, des frameworks tels que EggJs, NestJs et Midway ont été développés. Ce type de framework résume l'implémentation d'une application en un processus universel et extensible en implémentant le cycle de vie sous-jacent. Il suffit de suivre la méthode de configuration fournie par le framework pour implémenter l'application plus simplement. Le cadre met en œuvre le contrôle des processus du programme, et il nous suffit d'assembler nos pièces à l'endroit approprié. Cela ressemble davantage à un travail à la chaîne. Chaque processus est clairement divisé et de nombreux coûts de mise en œuvre sont économisés.

1.3 Résumé

Les deux étapes ci-dessus ne sont qu'une préfiguration. Nous pouvons à peu près comprendre que la mise à niveau du framework améliore l'efficacité de la production. Pour réaliser la mise à niveau du framework, quelques idées et modèles de conception seront introduits. Les concepts d'inversion de contrôle, d'injection de dépendances et de métaprogrammation sont apparus dans Nest. Parlons-en ci-dessous.

2 Inversion de contrôle et injection de dépendances

2.1 Injection de dépendances

Une application est en fait un ensemble de classes abstraites, qui réalisent toutes les fonctions de l'application en s'appelant les unes les autres. À mesure que la complexité du code et des fonctions de l'application augmente, le projet deviendra certainement de plus en plus difficile à maintenir car il y a de plus en plus de classes et les relations entre elles deviennent de plus en plus complexes.

Par exemple, si nous utilisons Koa pour développer notre application, Koa lui-même implémente principalement un ensemble de fonctionnalités de base du service Web. Dans le processus d'implémentation de l'application, nous définirons de nombreuses classes, les méthodes d'instanciation de ces classes et leur interdépendance. . Les relations seront librement organisées et contrôlées par notre logique de code. L'instanciation de chaque classe est nouvelle manuellement par nous, et nous pouvons contrôler si une classe n'est instanciée qu'une seule fois puis partagée, ou instanciée à chaque fois. La classe B suivante dépend de A. Chaque fois que B est instancié, A sera instancié une fois, donc pour chaque instance B, A est une instance qui n'est pas partagée.

class A{}
// B
class B{
    contructor(){
        this.a = new A();
    }
}
Copier après la connexion

Le C ci-dessous est l'instance externe obtenue, donc plusieurs instances C partagent l'instance app.a.

class A{}
// C
const app = {};
app.a = new A();
class C{
    contructor(){
        this.a = app.a;
    }
}
Copier après la connexion

Le D suivant est transmis via le paramètre constructeur. Vous pouvez transmettre une instance non partagée à chaque fois, ou vous pouvez transmettre l'instance app.a partagée (D et F share app.a), et depuis elle. est maintenant une méthode paramètre, je peux également transmettre une instance de classe X.

class A{}
class X{}
// D
const 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())
Copier après la connexion

Cette méthode est une injection de dépendance, qui injecte A, dont B dépend, dans B en passant une valeur. L'injection via le constructeur (passage par valeur) n'est qu'une méthode d'implémentation. Elle peut également être transmise en implémentant l'appel de la méthode set, ou toute autre méthode, à condition qu'une dépendance externe puisse être transmise à la dépendance interne. C'est vraiment aussi simple que cela.

class A{}
// D
class D{
    setDep(a){
        this.a = a;
    }
}
const d = new D()
d.setDep(new A())
Copier après la connexion

2.2 Tout en injection de dépendances ?

随着迭代进行,出现了 B 根据不同的前置条件依赖会发生变化。比如,前置条件一 this.a 需要传入 A 的实例,前置条件二this.a需要传入 X 的实例。这个时候,我们就会开始做实际的抽象了。我们就会改造成上面 D 这样依赖注入的方式。

初期,我们在实现应用的时候,在满足当时需求的情况下,就会实现出 B 和 C 类的写法,这本身也没有什么问题,项目迭代了几年之后,都不一定会动这部分代码。我们要是去考虑后期扩展什么的,是会影响开发效率的,而且不一定派的上用场。所以大部分时候,我们都是遇到需要抽象的场景,再对部分代码做抽象改造。

// 改造前
class B{
    contructor(){
        this.a = new A();
    }
}
new B()

// 改造后
class D{
    contructor(a){
        this.a = a;
    }
}
new D(new A())
new D(new X())
Copier après la connexion

按照目前的开发模式,CBD三种类都会存在,B 和 C有一定的几率发展成为 D,每次升级 D 的抽象过程,我们会需要重构代码,这是一种实现成本。

这里举这个例子是想说明,在一个没有任何约束或者规定的开发模式下。我们是可以自由的写代码来达到各种类与类之间依赖控制。在一个完全开放的环境里,是非常自由的,这是一个刀耕火种的原始时代。由于没有一个固定的代码开发模式,没有一个最高行动纲领,随着不同开发人员的介入或者说同一个开发者不同时间段写代码的差别,代码在增长的过程中,依赖关系会变得非常不清晰,该共享的实例可能被多次实例化,浪费内存。从代码中,很难看清楚一个完整的依赖关系结构,代码可能会变得非常难以维护。

Comprendre le mécanisme des modules du framework Node.js Nestjs et parler des principes de mise en œuvre

那我们每定义一个类,都按照依赖注入的方式来写,都写成 D 这样的,那 C 和 B 的抽象过程就被提前了,这样后期扩展也比较方便,减少了改造成本。所以把这叫All in 依赖注入,也就是我们所有依赖都通过依赖注入的方式实现。

可这样前期的实现成本又变高了,很难在团队协作中达到统一并且坚持下去,最终可能会落地失败,这也可以被定义为是一种过度设计,因为额外的实现成本,不一定能带来收益。

2.3 控制反转

既然已经约定好了统一使用依赖注入的方式,那是否可以通过框架的底层封装,实现一个底层控制器,约定一个依赖配置规则,控制器根据我们定义的依赖配置来控制实例化过程和依赖共享,帮助我们实现类管理。这样的设计模式就叫控制反转

控制反转可能第一次听说的时候会很难理解,控制指的什么?反转了啥?

猜测是由于开发者一开始就用此类框架,并没有体验过上个“Express、Koa时代”,缺乏旧社会毒打。加上这反转的用词,在程序中显得非常的抽象,难以望文生义。

前文我们说的实现 Koa 应用,所有的类完全由我们自由控制的,所以可以看作是一个常规的程序控制方式,那就叫它:控制正转。而我们使用 Nest,它底层实现一套控制器,我们只需要在实际开发过程中,按照约定写配置代码,框架程序就会帮我们管理类的依赖注入,所以就把它叫作:控制反转。

本质就是把程序的实现过程交给框架程序去统一管理,控制权从开发者,交给了框架程序。

控制正转:开发者纯手动控制程序

Comprendre le mécanisme des modules du framework Node.js Nestjs et parler des principes de mise en œuvre

控制反转:框架程序控制

Comprendre le mécanisme des modules du framework Node.js Nestjs et parler des principes de mise en œuvre

举个现实的例子,一个人本来是自己开车去上班的,他的目的就是到达公司。它自己开车,自己控制路线。而如果交出开车的控制权,就是去赶公交,他只需要选择一个对应的班车就可以到达公司了。单从控制来说,人就是被解放出来了,只需要记住坐那趟公交就行了,犯错的几率也小了,人也轻松了不少。公交系统就是控制器,公交线路就是约定配置。

通过如上的实际对比,我想应该有点能理解控制反转了。

2.4 小结

从 Koa 到 Nest,从前端的 JQuery 到 Vue React。其实都是一步步通过框架封装,去解决上个时代低效率的问题。

上面的 Koa 应用开发,通过非常原始的方式去控制依赖和实例化,就类似于前端中的 JQuery 操作 dom ,这种很原始的方式就把它叫控制正转,而 Vue React 就好似 Nest 提供了一层程序控制器,他们可以都叫控制反转。这也是个人理解,如果有问题期望大神指出。

下面再来说说 Nest 中的模块 @Module,依赖注入、控制反转需要它作为媒介。

3 Nestjs的模块(@Module)

Nestjs实现了控制反转,约定配置模块(@module)的 imports、exports、providers 管理提供者也就是类的依赖注入。

providers 可以理解是在当前模块注册和实例化类,下面的 A 和 B 就在当前模块被实例化,如果B在构造函数中引用 A,就是引用的当前 ModuleD 的 A 实例。

import { Module } from '@nestjs/common';
import { ModuleX } from './moduleX';
import { A } from './A';
import { B } from './B';

@Module({
  imports: [ModuleX],
  providers: [A,B],
  exports: [A]
})
export class ModuleD {}

// B
class B{
    constructor(a:A){
        this.a = a;
    }
}
Copier après la connexion

exports 就是把当前模块中的 providers 中实例化的类,作为可被外部模块共享的类。比如现在 ModuleF 的 C 类实例化的时候,想直接注入 ModuleD 的 A 类实例。就在 ModuleD 中设置导出(exports)A,在 ModuleF 中通过 imports 导入 ModuleD。

按照下面的写法,控制反转程序会自动扫描依赖,首先看自己模块的 providers 中,有没有提供者 A,如果没有就去寻找导入的 ModuleD 中是否有 A 实例,发现存在,就取得 ModuleD 的 A 实例注入到 C 实例之中。

import { Module } from '@nestjs/common';
import { ModuleD} from './moduleD';
import { C } from './C';

@Module({
  imports: [ModuleD],
  providers: [C],
})
export class ModuleF {}

// C
class C {
    constructor(a:A){
        this.a = a;
    }
}
Copier après la connexion

因此想要让外部模块使用当前模块的类实例,必须先在当前模块的providers里定义实例化类,再定义导出这个类,否则就会报错。

//正确
@Module({
  providers: [A],
  exports: [A]
})
//错误
@Module({
  providers: [],
  exports: [A]
})
Copier après la connexion

后期补充

模块查找实例的过程回看了一下,确实有点不清晰。核心点就是providers里的类会被实例化,实例化后就是提供者,模块里只有providers里的类会被实例化,而导出和导入只是一个组织关系配置。模块会优先使用自己的提供者,如果没有,再去找导入的模块是否有对应提供者

这里还是提一嘴ts的知识点

export class C {
  constructor(private a: A) {
  }
}
Copier après la connexion

由于 TypeScript 支持 constructor 参数(private、protected、public、readonly)隐式自动定义为 class 属性 (Parameter Property),因此无需使用 this.a = a。Nest 中都是这样的写法。

4 Nest 元编程

元编程的概念在 Nest 框架中得到了体现,它其中的控制反转、装饰器,就是元编程的实现。大概可以理解为,元编程本质还是编程,只是中间多了一些抽象的程序,这个抽象程序能够识别元数据(如@Module中的对象数据),其实就是一种扩展能力,能够将其他程序作为数据来处理。我们在编写这样的抽象程序,就是在元编程了。

4.1 元数据

Nest 文档中也常提到了元数据,元数据这个概念第一次看到的话,也会比较费解,需要随着接触时间增长习惯成理解,可以不用太过纠结。

元数据的定义是:描述数据的数据,主要是描述数据属性的信息,也可以理解为描述程序的数据。

Nest 中 @Module 配置的exports、providers、imports、controllers都是元数据,因为它是用来描述程序关系的数据,这个数据信息不是展示给终端用户的实际数据,而是给框架程序读取识别的。

4.2 Nest 装饰器

如果看看 Nest 中的装饰器源码,会发现,几乎每一个装饰器本身只是通过 reflect-metadata 定义了一个元数据。

@Injectable装饰器

export function Injectable(options?: InjectableOptions): ClassDecorator {
  return (target: object) => {
    Reflect.defineMetadata(INJECTABLE_WATERMARK, true, target);
    Reflect.defineMetadata(SCOPE_OPTIONS_METADATA, options, target);
  };
}
Copier après la connexion

这里存在反射的概念,反射也比较好理解,拿 @Module 装饰器举例,定义元数据 providers,只是往providers数组里传入了类,在程序实际运行时providers里的类,会被框架程序自动实例化变为提供者,不需要开发者显示的去执行实例化和依赖注入。类只有在模块中实例化了之后才变成了提供者。providers中的类被反射了成了提供者,控制反转就是利用的反射技术。

换个例子的话,就是数据库中的 ORM(对象关系映射),使用 ORM 只需要定义表字段,ORM 库会自动把对象数据转换为 SQL 语句。

const data = TableModel.build();

data.time = 1;
data.browser = 'chrome';
    
data.save();
// SQL: INSERT INTO tableName (time,browser) [{"time":1,"browser":"chrome"}]
Copier après la connexion

ORM 库就是利用了反射技术,让使用者只需要关注字段数据本身,对象被 ORM 库反射成为了 SQL 执行语句,开发者只需要关注数据字段,而不需要去写 SQL 了。

4.3 reflect-metadata

reflect-metadata 是一个反射库,Nest 用它来管理元数据。reflect-metadata 使用 WeakMap,创建一个全局单实例,通过 set 和 get 方法设置和获取被装饰对象(类、方法等)的元数据。

// 随便看看即可
var _WeakMap = !usePolyfill && typeof WeakMap === "function" ? WeakMap : CreateWeakMapPolyfill();   

var Metadata = new _WeakMap();
function defineMetadata(){
    OrdinaryDefineOwnMetadata(){
        GetOrCreateMetadataMap(){
          var targetMetadata = Metadata.get(O);
            if (IsUndefined(targetMetadata)) {
                if (!Create)
                    return undefined;
                targetMetadata = new _Map();
                Metadata.set(O, targetMetadata);
            }
            var metadataMap = targetMetadata.get(P);
            if (IsUndefined(metadataMap)) {
                if (!Create)
                    return undefined;
                metadataMap = new _Map();
                targetMetadata.set(P, metadataMap);
            }
            return metadataMap;
        }
    }
}
Copier après la connexion

reflect-metadata 把被装饰者的元数据存在了全局单例对象中,进行统一管理。reflect-metadata 并不是实现具体的反射,而是提供了一个辅助反射实现的工具库。

5 最后

现在再来看看前面的几个疑问。

  • 为什么需要控制反转?

  • 什么是依赖注入?

  • 装饰器做了啥?

  • 模块 (@Module) 中的提供者(providers),导入(imports)、导出(exports)是什么实现原理?

1 和 2 我想前面已经说清楚了,如果还有点模糊,建议再回去看一遍并查阅一些其它文章资料,通过不同作者的思维来帮助理解知识。

5.1 问题 [3 4] 总述:

Nest 利用反射技术、实现了控制反转,提供了元编程能力,开发者使用 @Module 装饰器修饰类并定义元数据(providers\imports\exports),元数据被存储在全局对象中(使用 reflect-metadata 库)。程序运行后,Nest 框架内部的控制程序读取和注册模块树,扫描元数据并实例化类,使其成为提供者,并根据模块元数据中的 providers\imports\exports 定义,在所有模块的提供者中寻找当前类的其它依赖类的实例(提供者),找到后通过构造函数注入。

本文概念较多,也并没有做太详细的解析,概念需要时间慢慢理解,如果一时理解不透彻,也不必太过着急。好吧,就到这里,这篇文章还是花费不少精力,喜欢的朋友期望你能一键三连~

更多node相关知识,请访问:nodejs 教程

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Étiquettes associées:
source:juejin.cn
Déclaration de ce site Web
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn
Tutoriels populaires
Plus>
Derniers téléchargements
Plus>
effets Web
Code source du site Web
Matériel du site Web
Modèle frontal