s

sorryljt

V1

2022/06/16阅读:30主题:雁栖湖

Nestjs + MongoDB 在内部低代码平台的落地实践

前言

NodeJs在前端领域的作用不言而喻,做为前端的“基石”,在打包工具、脚手架、前端工程中间层(模板渲染、接口转发)、Server端中应用广泛,掌握NodeJs开发技能已经是每个前端开发者的必备技能之一,今天这篇文章,就介绍一下,内部自研项目使用NodeJs全栈开发的落地经历。

背景

在低代码平台的浪潮中,我司也自研了自己的低代码平台,其中,前端采用可视化拖拽引擎 + 解析器的模式,以jsonSchema为媒介,来实现低代码开发模式,这里就需要一个稳定的Server端来支持其持久化数据存储以及其平台业务流转。我们采用了NestJs + MongoDB的技术方案,下面是一个简单的架构图,一些技术细节不便公开,所以web层的技术架构简写,本文我们只介绍Server端

其中

  • 数据库使用MongoDB
  • Server端核心框架使用NestJs
  • oa登录使用内部express中间件
  • 前端部署使用node模板
  • 对接第三方(如:权限系统)使用scf

技术选型

🤔 为什么是NestJs?

首先,nestjs是基于express的一款企业级应用框架(默认express,还可支持Fastify,本文只谈express),nodejs的完整框架有很多,如expresskoa2eggjs等。但我们选择了nestjs,其主要原因是内部生态;我们知道,expresskoa2中间件的基本语法不同,express是基于promise的,而koa2是基于async/await的,所以,express中间件和koa2中间件是不通用的。而团队内部的沉淀的中间件(如:登录中间件)都是express生态,这是我们选择nestjs最主要原因;另外,nestjs的周边生态丰富、并完全支持typescript,于是我们坚定的使用了nestjs。

🤔为什么是MongoDB?

行业内有很多优秀的数据库,如MySql等,但我们选择了MongoDB,其主要原因是,相对简单灵活且轻量,因为我们的低代码平台是一个专注于辅助开发的功能性平台,非服务型产品,所以在数据性能方面没有太苛刻的要求,同时,因为我们的所有开发人员为FE,考虑到”易上手“的实际因素,而nestjs对MongoDB的支持又很完善(@nestjs/mongoose),

简述NestJs是如何运行的?

1️⃣ 入口文件

和所有Nodejs框架一样,NestJs也是需要一个入口文件,来完成一个“服务”的初始化,我们需要用到Nestjs为我们提供的工厂函数。于是,一个Nestjs的入口文件,大概是这样的

import { NestFactory } from '@nestjs/core';
import { NestExpressApplication } from '@nestjs/platform-express';
import { AppModule } from './app.module';

async function bootstrap({
    const app = await NestFactory.create<NestExpressApplication>(AppModule);
    // ...
    // 各种express或者nest中间件
    // ...
    await app.listen(8001);
}
module.exports = bootstrap;

2️⃣ 模块加载

从上面的初始化代码,我们可以看到,除了两个nestjs自带工厂函数以及泛型外,还有一个我们自己的文件“app.module”。 这里不得不说一下,Nest的本质,是一个MVC框架,而在Nestjs中,"M"更倾向于“模块”的概念;所以,上面的初始化代码,可以理解为整个项目的“模块加载”,而NestJs又采用了“装饰器”的语法来对类做修饰;下面来看一下,Nestjs如何加载全部模块,以及在全局应用某些逻辑

import { Module, MiddlewareConsumer, NestModule } from '@nestjs/common';
import { Controller } from './app.controller'

@Module({
  imports: [
    // 在这里引入其他模块,以及数据库相关配置
  ],
  controllers: [Controller], // 这里可作为全局的Controller
  providers: [
    // 依赖注入;全局注入一些依赖逻辑,如:全局响应拦截器等,其中依赖注入的逻辑,Nest会帮我们处理好模块之间的依赖等关系,可以直接使用,不必手动梳理
    APP_PIPE
  ],
})
export class AppModule implements NestModule {
  configure(consumer: MiddlewareConsumer) {
    consumer
      .apply(
        // 一些自定义的中间件,同app.use,但自定义中间件放在这里更符合开发规范
      )
  }
}

其中,依赖注入又是NestJs最核心的功能之一,一句话概况就是,依赖注入是代码解耦 (Decoupling) 的一种实现方式,通过将依赖的 class 的构造函数调用移动到调用方 class 外部的方式,来实现调用方 class 和依赖 class 解耦。因为这里面知识点比较多,且非本文重点,这里不做太多详细介绍,有兴趣的可以看一下这篇文章Nest.js 依赖注入

3️⃣ Controller

和 MVC 中 Controller 的概念一样;可以理解为是服务的入口和出口。一般来说都是薄薄的一层,主要起到了路由 (Routing) 的作用。当然,其和imports 中模块内部自身的Controller是共存的;

4️⃣ 连接数据库

通过上面的一个简单介绍,大家可以了解到,一个简单的NestJs程序是什么样的流转逻辑,当然了,实际开发中,我们的模块会非常多,这时只需要在imports 中引入即可,每一个模块(module)的结构也是个上面提到的一样,imports不仅可以引入其他模块,还可以引入数据库的配置,即该模块需要链接哪几张表。以实际代码为例,我们来看一下如何在NestJs对应模块中连接MongoDB数据库表

import { MongooseModule } from '@nestjs/mongoose';

@Module({
  imports: [
    MongooseModule.forFeature([
      {
        name: ManuScript.name,
        schema: ManuScriptSchema,
        collection: 't_manuscript'// 指定collection 表名称(不指定默认加s)
      },
      {
        name: PageGroup.name,
        schema: PageGroupSchema,
        collection: 't_manuscript_pagegroup',
      },
  ],
})

其中,

  • “schema”可以理解为表实例,里面描述着表结构,就像这样
import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose';
import { Document, Types } from 'mongoose';

export type MemoDocument = Memo & Document;

@Schema({
  timestamps: true,
})
export class Memo extends Document {
  @Prop({
    default: Types.ObjectId,
    index: true
  })
  memoId: string

  @Prop()
  designerItemId: string// 关联组件标识id

  @Prop()
  docId: string// 关联文稿id
}
export const MemoSchema = SchemaFactory.createForClass(Memo);

每一个@Prop下都是一个“表字段”,我们也可以将某一个字段设置为索引。

  • “ collection”代表表的“表名”,我们只需要在代码里这样创建即可,不需要提前创建表,也不需要提前设计定义字段,最终的表现是这样的 这里我们使用的“MongoDB Compass”作为MongoDB的可视化工具。

5️⃣ 模板渲染

了解了上面的基本操作,那么你已经可以创建一个可用的NestJs应用了,对于Node来说,模板渲染能力也是其其中一个主要能力,我们利用该能力来渲染前端静态资源,这里会在入口文件映入一个中间件,如下

  app.setBaseViewsDir(join(__dirname, '..''views'));
  app.setViewEngine('html');
  app.engine('html'require('shtm').__express);

意思是,使用shtm模板渲染插件,来渲染html。同样的,渲染模板是也是需要一个“路由”或者“Controller”的path用来指定对应的渲染逻辑,如下

import { Controller, Get, Render } from '@nestjs/common';
  @Get('/main(/*)?'// 路由,到这个路由时,执行下面的渲染逻辑
  @Render('main'// 要渲染哪个html模板(文件名)。
  @ApiOperation({ summary: '首页路由' })
  main() {
    return {
      // 需要传到模板中的一些数据
    };
  }

Nest为我们的项目落地提供了哪些便利

✅ 完整丰富的生态

因为是内部创新型项目,好多东西都存在在未知的探索,nestjs为我们提供了强大的生态支持,为我们不断的完善项目功能提供强大的保障;如项目中需要SCF(长链接)来对接公司内部一些第三方服务,我们可以通过优秀的中间件机制轻松的完成;如项目中需要集成websocket,nest也有@nestjs/websockets这样的能力来提供支持;如项目中,需要使用mongoose,nest也有@nestjs/mongoose这样的能力来提供支持。这样为我们的项目创新以及扩展提供了强大的技术生态保障

✅ 优秀的团队代码管理

上面提到了,NestJs中模块化的运行机制,这种机制下, 对于团队开发的代码管理具有很大的优势,我们可以每个人负责一个相应的模块(module),在代码管理层面上,显得非常高效

写在后面

本文简述了NestJs在我们内部自研项目中的落地落地实践经历,并介绍了如何创建一个NestJs应用,如果看到文章的你,也有使用NodeJs搭建一个Server服务的想法,不妨试一试,NestJs吧。

分类:

前端

标签:

Node.js

作者介绍

s
sorryljt
V1