nestjs系列三:nestjs如何通过Module组织代码结构?

385 阅读5分钟

我们知道,后端框架基本都是 MVC 的架构, 即 Model View Controller 的简写。

在 MVC 架构下,请求会先发送给 Controller,由它调度 Model 层的 Service 来完成业务逻辑,然后返回对应的 View。 如下图所示:

这个图中,并没有出现Module的概念,那Module是什么呢?

首先需要弄明白nestjs是什么? 它和我们常见的expresskoa是同一个东西么?

首先,我们可以明确的一点,expresskoa是基于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的服务,虽然我们调的是expressapplicationlisten方法,但其实底层用的就是我们熟悉的Node Http模块。

另外,expresskoa都给我们内置了路由模块,我们不需要再去手动实现路由的分发。然后还有中间件、静态文件处理等模块,都提高了我们开发Node项目的效率。

总体来说,expresskoa与Node的关系就像是jQueryJavaScript间的关系类似,我们总会使用它们来加快我们项目的开发效率,但是我们不一定会用它们来约束我们的开发范式。

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是如何组织的。

image.png

每个应用程序至少有一个模块,即根模块,根模块是 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其他模块了,每个模块都有自己的输入和输出。

image.png

首先要明确,每个模块肯定要输出一些东西的,不然就无法把模块组装起来来完成一个大型任务了。那输出的是什么呢?

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时,只需要记住:

  1. 定义自己的controllers

  2. 定义自己的service

  3. 把自己的service放在providers中,这样controllers才能用service

  4. 如果需要用外部的service,将外部module放在imports里

  5. 如果自己的service也需要被其他module使用,将自己的service放到exports里