我们知道,后端框架基本都是 MVC 的架构, 即 Model View Controller 的简写。
在 MVC 架构下,请求会先发送给 Controller,由它调度 Model 层的 Service 来完成业务逻辑,然后返回对应的 View。 如下图所示:
这个图中,并没有出现Module
的概念,那Module
是什么呢?
首先需要弄明白nestjs
是什么? 它和我们常见的express
、koa
是同一个东西么?
首先,我们可以明确的一点,express
和koa
是基于NodeJs的开发框架,也就是说,它们其实是对Node的一层封装,并提供API用于完成某些基于Node的复合操作。
举个例子:
const express = require('express')
const app = express()
const port = 3000
app.listen(port, () => {
console.log(`Example app listening on port ${port}`)
})
通过上面的代码,我们很容易的就在本地起了一个监听端口为3000的服务,虽然我们调的是express
里application
的listen
方法,但其实底层用的就是我们熟悉的Node Http
模块。
另外,express
和koa
都给我们内置了路由模块,我们不需要再去手动实现路由的分发。然后还有中间件、静态文件处理等模块,都提高了我们开发Node
项目的效率。
总体来说,express
、koa
与Node的关系就像是jQuery
和JavaScript
间的关系类似,我们总会使用它们来加快我们项目的开发效率,但是我们不一定会用它们来约束我们的开发范式。
nest
则是更上层次的封装,跟eggjs
一样(eggjs
对 Typescript 支持不太好,其次它已经不被维护了,前端同学可以直接入门nest就好),它提供了一套完整的开箱即用的开发范式(也可以理解成应用结构体系),可以让我们创建、维护大型、高拓展性、低耦合性的应用。
如果我们用的是express
或者koa
,并且我们没有及时的完善我们的开发协作规范,就容易会让项目走向失控,但是nest
帮我们很好的解决了这个问题,只要我们用了这个框架,就必须得看制定的规范进行开发协作。
可以这么理解,express
或者koa
更适合一些个人小项目,而nest
专为大项目而生的。
那nest
通过什么方式来规范开发呢?
那就是通过Module
。那什么是Module
?
Module
模块是一个用 @Module() 装饰器注释的类。
@Module() 装饰器提供了元数据,Nest 利用这些元数据来组织应用程序结构。
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
@Module({
imports: [],
controllers: [AppController],
providers: [AppService],
exports: []
})
export class AppModule {}
下面是官网的一张图,很直观的描述了nest
是如何组织的。
每个应用程序至少有一个模块,即根模块,根模块是 Nest 用来构建应用程序图的起点。
官方文档强烈推荐使用模块作为组织组件的有效方式。
因此,对于大多数应结果序来说,最终的架构将使用多个模块,每个模块都封装一组紧密相关的功能。这对于开发维护大型项目提供了基础。
总之,Nest的大致理念就是一颗模块树,从根模块出发,连接到许多的子功能模块。就跟我们分解项目任务一样,一个任务就是一个模块,从而很多任务组合在一起就形成了一个项目。
那一个模块包含哪些呢? 通过如下代码进行解释。
// son.module.ts
import { Module } from '@nestjs/common';
import { SonService } from './son.service';
import { SonController } from './son.controller';
@Module({
imports: [],
controllers: [SonController],
providers: [SonService],
exports: []
})
export class SonModule {}
// son.controller.ts
import { Controller,Get, Post, Body} from '@nestjs/common';
import { SonService } from './son.service';
import { CreateSonDto } from './dto/create-son.dto';
import { UpdateSonDto } from './dto/update-son.dto';
@Controller('son')
export class SonController {
constructor(private readonly sonService: SonService) {}
@Post()
create(@Body() createSonDto: CreateSonDto) {
return this.sonService.create(createSonDto);
}
@Get()
findAll() {
return this.sonService.findAll();
}
@Get(':id')
findOne(@Param('id') id: string) {
return this.sonService.findOne(+id);
}
}
// son.service.ts
import { Injectable } from '@nestjs/common';
import { CreateSonDto } from './dto/create-son.dto';
import { UpdateSonDto } from './dto/update-son.dto';
@Injectable()
export class SonService {
create(createSonDto: CreateSonDto) {
return 'This action adds a new son';
}
findAll() {
return `This action returns all son`;
}
}
上面这段代码就是一个标准模块的组成,它是为了完成了一定任务。它主要是两部分:Controller
和 Service
。Controller是接收请求的入口,Service则是方法实现。
首先,SonService
被@Injectable()
装饰,并被放入了module定义的providers
中,表示SonService
可以被注入;
其次,SonController
在构造函数中传入了一个SonService
类的实例,这样就写可以调用这个对象上的方法this.sonService.findAll();
,这都是nest框架帮我们做好了,不需要我们去new SonService()
来显示的创建一个实例对象,这个操作叫控制反转。
如果你想在SonModule
中使用其他的模块,那就需要imports
其他模块了,每个模块都有自己的输入和输出。
首先要明确,每个模块肯定要输出一些东西的,不然就无法把模块组装起来来完成一个大型任务了。那输出的是什么呢?
Module输出的就是Service。
// app.module.ts
@Module({
// 输入
imports: [SonModule],
controllers: [AppController],
providers: [ AppService ],
})
// son.module.ts
@Module({
controllers: [SonController],
providers: [SonService],
// 输出
exports: [SonService],
})
注意,
imports
只能引入Module
,如SonModule
,不能引入control和service。
在app.module.ts
中,其实可以使用两个service,分别是imports里面的,一个是providers里面的,如果我们去掉imports
,将SonService
也放到providers
中,我们的代码依然可以运行:
@Module({
// 输入
imports: [],
controllers: [AppController],
providers: [ AppService, SonService ],
})
那到底怎么用呢?
只要是用外部服务(也就是除去appModule将appService作为Provider这种情况),使用import更为合适。
区别在于:import module会复用已经创建的实例,而每个provider都会创建新的实例。
前者占用更少的内存,且一个可复用的实例在多处被使用在需求中也更加常见。
到这里,基本上就入门nestjs,看一个Nestjs应用是怎么跑的,首先就是看明白Module之间是如何互相作用的。
那么在实现我们自己的Module时,只需要记住:
-
定义自己的controllers
-
定义自己的service
-
把自己的service放在providers中,这样controllers才能用service
-
如果需要用外部的service,将外部module放在imports里
-
如果自己的service也需要被其他module使用,将自己的service放到exports里