Fahami mekanisme modul rangka kerja Node.js Nestjs dan bercakap tentang prinsip pelaksanaan

青灯夜游
Lepaskan: 2022-04-25 20:59:03
ke hadapan
3152 orang telah melayarinya

Artikel ini akan membawa anda melalui Node rangka kerja belakang Nest.js dan memperkenalkan konsep dan prinsip pelaksanaan mekanisme modul Nestjs saya harap ia akan membantu semua orang.

Fahami mekanisme modul rangka kerja Node.js Nestjs dan bercakap tentang prinsip pelaksanaan

Nest menyediakan mekanisme modul Suntikan Ketergantungan diselesaikan dengan mentakrifkan pembekal, import, eksport dan pembina pembekal dalam penghias modul, dan keseluruhan pepohon modul disusun Pembangunan aplikasi. Tidak ada masalah untuk membina aplikasi secara langsung mengikut konvensyen rangka kerja itu sendiri. Walau bagaimanapun, bagi saya, saya merasakan bahawa saya tidak mempunyai pemahaman yang lebih jelas dan sistematik tentang suntikan pergantungan, penyongsangan kawalan, modul, pembekal, metadata, penghias berkaitan, dsb. yang diisytiharkan oleh rangka kerja.

  • Mengapa penyongsangan kawalan diperlukan?
  • Apakah suntikan pergantungan?
  • Apa yang penghias lakukan?
  • Apakah prinsip pelaksanaan pembekal, import dan eksport dalam modul (@Modul)?

Nampaknya saya boleh faham dan menghayatinya, tetapi biarlah saya terangkan dengan jelas dari awal, saya tidak dapat menjelaskannya dengan jelas. Jadi saya membuat kajian dan menghasilkan artikel ini. Mulai sekarang, kita mulakan dari awal dan masukkan teks utama.

1 Dua peringkat

1.1 Ekspres, Koa

Proses pembangunan bahasa dan komuniti teknikalnya mestilah The perkembangan fungsi secara beransur-ansur dari bawah ke atas adalah seperti proses akar pokok perlahan-lahan tumbuh menjadi dahan dan kemudian penuh dengan daun. Terdahulu, rangka kerja perkhidmatan web asas seperti Express dan Koa muncul dalam Nodejs. Mampu memberikan keupayaan perkhidmatan yang sangat asas. Berdasarkan rangka kerja sedemikian, sejumlah besar perisian tengah dan pemalam mula dilahirkan dalam komuniti, menyediakan perkhidmatan yang lebih kaya untuk rangka kerja tersebut. Kita perlu mengatur kebergantungan aplikasi dan membina perancah aplikasi sendiri, yang fleksibel dan menyusahkan, dan juga memerlukan beban kerja tertentu.

Kemudian dalam pembangunan, beberapa rangka kerja dengan pengeluaran yang lebih cekap dan peraturan yang lebih bersatu telah dilahirkan, membuka peringkat yang lebih baharu.

1.2 EggJs, Nestjs

Untuk menjadi lebih mudah disesuaikan dengan aplikasi pengeluaran pantas, menyatukan spesifikasi dan bekerja di luar kotak, EggJs dan NestJ telah dibangunkan , Midway dan rangka kerja lain. Rangka kerja jenis ini merumuskan pelaksanaan aplikasi ke dalam proses universal dan boleh diperluaskan dengan melaksanakan kitaran hayat asas Kami hanya perlu mengikut kaedah konfigurasi yang disediakan oleh rangka kerja untuk melaksanakan aplikasi dengan lebih mudah. Rangka kerja melaksanakan kawalan proses program, dan kami hanya perlu memasang bahagian kami di lokasi yang sesuai Ini lebih kelihatan seperti kerja barisan pemasangan Setiap proses dibahagikan dengan jelas, dan banyak kos pelaksanaan dijimatkan.

1.3 Ringkasan

Dua peringkat di atas hanyalah bayangan awal Kami boleh memahami secara kasar bahawa peningkatan rangka kerja meningkatkan kecekapan pengeluaran menaik taraf rangka kerja, beberapa idea dan corak reka bentuk akan diperkenalkan Konsep penyongsangan kawalan, suntikan kebergantungan dan pengaturcaraan meta muncul dalam Nest. Mari kita bincangkan di bawah.

2 Penyongsangan Kawalan dan Suntikan Kebergantungan

2.1 Suntikan Ketergantungan

Aplikasi praktikal Di sana adalah banyak kelas abstrak yang menyedari semua fungsi aplikasi dengan memanggil satu sama lain. Apabila kerumitan kod dan fungsi aplikasi meningkat, projek itu pasti akan menjadi lebih dan lebih sukar untuk dikekalkan kerana terdapat lebih banyak kelas dan hubungan antara mereka menjadi lebih dan lebih kompleks.

Sebagai contoh, jika kita menggunakan Koa untuk membangunkan aplikasi kita, Koa sendiri terutamanya melaksanakan satu set keupayaan perkhidmatan Web asas Dalam proses melaksanakan aplikasi, kita akan mentakrifkan banyak kelas dan instantiasi kelas ini kaedah dan saling bergantung akan secara bebas diatur dan dikawal oleh kami dalam logik kod. Instalasi setiap kelas adalah baharu secara manual oleh kami dan kami boleh mengawal sama ada kelas hanya dibuat seketika dan kemudian dikongsi, atau digunakan setiap kali. Kelas B berikut bergantung pada A. Setiap kali B di instantiated, A akan instantiated sekali, jadi untuk setiap instance B, A ialah instance yang tidak dikongsi.

class A{}
// B
class B{
    contructor(){
        this.a = new A();
    }
}
Salin selepas log masuk

C di bawah ialah tika luaran yang diperoleh, jadi beberapa tika C berkongsi tika app.a.

class A{}
// C
const app = {};
app.a = new A();
class C{
    contructor(){
        this.a = app.a;
    }
}
Salin selepas log masuk

D berikut dihantar melalui parameter pembina Anda boleh lulus dalam contoh bukan kongsi setiap kali, atau anda boleh lulus dalam app.a yang dikongsi (apl kongsi D dan F. a) , dan kerana ia kini diluluskan sebagai parameter, saya juga boleh lulus dalam contoh kelas 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())
Salin selepas log masuk

Kaedah ini ialah Suntikan Kebergantungan, yang menyuntik A, yang bergantung pada B, ke dalam B dengan menghantar nilai. Suntikan melalui pembina (melepasi nilai) hanyalah satu kaedah pelaksanaan Ia juga boleh dihantar dengan melaksanakan panggilan kaedah yang ditetapkan, atau mana-mana kaedah lain, selagi kebergantungan luaran boleh dihantar ke dalam yang dalaman. Ia sangat mudah.

class A{}
// D
class D{
    setDep(a){
        this.a = a;
    }
}
const d = new D()
d.setDep(new A())
Salin selepas log masuk

2.2 Semua dalam suntikan pergantungan?

随着迭代进行,出现了 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())
Salin selepas log masuk

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

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

Fahami mekanisme modul rangka kerja Node.js Nestjs dan bercakap tentang prinsip pelaksanaan

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

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

2.3 控制反转

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

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

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

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

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

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

Fahami mekanisme modul rangka kerja Node.js Nestjs dan bercakap tentang prinsip pelaksanaan

控制反转:框架程序控制

Fahami mekanisme modul rangka kerja Node.js Nestjs dan bercakap tentang prinsip pelaksanaan

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

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

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;
    }
}
Salin selepas log masuk

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;
    }
}
Salin selepas log masuk

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

//正确
@Module({
  providers: [A],
  exports: [A]
})
//错误
@Module({
  providers: [],
  exports: [A]
})
Salin selepas log masuk

后期补充

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

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

export class C {
  constructor(private a: A) {
  }
}
Salin selepas log masuk

由于 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);
  };
}
Salin selepas log masuk

这里存在反射的概念,反射也比较好理解,拿 @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"}]
Salin selepas log masuk

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;
        }
    }
}
Salin selepas log masuk

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 教程

Atas ialah kandungan terperinci Fahami mekanisme modul rangka kerja Node.js Nestjs dan bercakap tentang prinsip pelaksanaan. Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP!

Label berkaitan:
sumber:juejin.cn
Kenyataan Laman Web ini
Kandungan artikel ini disumbangkan secara sukarela oleh netizen, dan hak cipta adalah milik pengarang asal. Laman web ini tidak memikul tanggungjawab undang-undang yang sepadan. Jika anda menemui sebarang kandungan yang disyaki plagiarisme atau pelanggaran, sila hubungi admin@php.cn
Tutorial Popular
Lagi>
Muat turun terkini
Lagi>
kesan web
Kod sumber laman web
Bahan laman web
Templat hujung hadapan
Tentang kita Penafian Sitemap
Laman web PHP Cina:Latihan PHP dalam talian kebajikan awam,Bantu pelajar PHP berkembang dengan cepat!