本人是一名 Node.js 实习生,在进入大搜车之后,有幸见识到 Akyuu.js 这个框架。但是这个框架是使用 Express + Callback 的方式,我不是很喜欢。在我的推荐以及社区的发展下,组长决定用 TS + Async/Await 来试一试。于是我也去了解了一下 TS 的后端框架有哪些,结果经过别人推荐,找到了 Nest.js 这个想法几乎和我一模一样的框架。

框架简介

因为我这个不是教程向,所以就不细讲,可以查看 Nest.js 官网。从我的感性角度来讲,简单说一下以下几个特点:

  • 去中心化路由。所有的路由通过装饰器与 Controller 绑定。简单、明了,学习成本低。
  • TypeScript/Rx.js 加持。智能补全,代码分析,静态类型等等优点。如果你只是个人用用的话,可能会觉得很全。但是放在企业当中使用,是非常大的优点。
  • 依赖注入。从 Angular 那里学习而来,但是进行了一些简化,但是完全够用。比如说简化掉了 deps。
  • 模块思想。Node 社区的后端框架,其实都被 Express 导向到了中间件的模式。而 Nest.js 却从 Angular 当中吸取到了模块的思想。不同的 Service、Controller、Component 组成不同的模块。模块之间可以相互依赖,也可以独立存在,这大大减少了测试和逻辑的复杂度。
  • 易于扩展。以往的框架,你能做的就是编写业务逻辑,而其他的你都很难去做到。于是传统的后端框架不得不引入了一套插件机制来增强框架的扩展性。但是 Nest.js 将插件的功能直接内置到了框架当中。传统的插件在这里可以认为就是一个模块,通过加载不同的模块来添加不同的功能。
  • Express 基石。有人会说,不是现在 Koa 才是更好的模型么?洋葱模型可以解决更多复杂的问题。没错,我不反对这个言论。但是我想说的是,Express 还是最简单最通用的方式,因为他不赖 Generator/Promise,只需要你又一个 Node.js 运行环境,支持 Callback 就可以了。(话说应该没有不支持 Callback 的 Node.js 环境吧,哈哈哈)不管怎么样,Express 的覆盖面还是比 Koa 要广不少。
  • 条条大路通罗马。那么有人就问了,那我要实现洋葱模型怎么办呢?我想说,办法总是会有的。而在 Nest.js 当中,通过 Interceptor ,可以很好的实现洋葱模型。也就是说你可以通过 Interceptor 来记录请求的耗时。
  • 同步代码。这里所说的同步代码并不是单单指的是 async/await。在很多支持 async/await 的框架中,如果你想返回值,如果是 Express ,你还是需要调用 resp.send(await getValue()),而 koa 也是需要调用 ctx.body = await getValue()。但是在 Nest.js 中,只需要 return await getValue() 即可。实现真正的同步编写业务逻辑代码。
  • 逻辑分层。其实很多功能,都是可以通过中间件来实现的。但是不同类型的功能有不同的需求,如果只是通过中间件来实现,势必会导致有一些重复的代码。于是 Nest.js 里面引入了 Pipe/Interceptor/Guard/ExceptionFilter 等逻辑层。不同的层里面处理相似的事情,比如说 Pipe 处理的是输入数据的转换。而 Interceptor 来实现洋葱模型。Guard 用于权限校验等拦截任务。ExceptionFilter 用来处理错误数据。这种分层带来的好处就是可以让代码更加清晰,主需要思考这个层需要做的事情,而不需要站在中间件的层面去考虑这个事情。
  • Validation。自带校验,而且和 TS 结合的非常完美,使用起来很舒服,请看教程
  • 输入参数的转换。这个其实是一个很方便的方面。有的时候你需要将输入的参数转换成一个类,这个时候你就可以通过 Validation 进行转换。你要是不想用自动转换,可以通过传统的手动转换的方式。
  • 测试功能完美。由于采用了依赖注入,所以测试简直简单的不得了。而且官方也提供了一系列测试工具,也能很好的解决单元测试的问题。

Nest.js 企业化当中的问题

  • 目录无约束。在企业当中,不对目录进行约束会导致代码越来越乱。从而降低了代码可维护性。
  • 没有配置管理功能。在框架开发中,配置往往是一个很重要的功能。比如说配置数据库的连接,配置监听的端口。
  • 没有进程管理。虽然有提供 @nestjs/cli,但是这个提供的仅仅是一个项目的创建的能力。
  • 部分文档讲解不详细,会提高入门的门槛。

不过总的来说,前面几点也正是 Nest.js 灵活性的保证。但是我们真正在开发当中,还是需要一种合理的约束来保证开发的统一。

Nest.js 企业化的尝试

那么我们这里针对上面的几个问题,尝试采用一些方式来进行约束。

目录结构

我们对项目指定如下的规则:

  • 全部通过 TypeScript 书写,并且全部位于 src 目录下
  • 入口文件是 main.ts 如果没有特殊情况,不动这个文件
  • 配置放在 src/config 文件夹下
  • 所有的 Service/Controller/Logic/Component 等都挂载到 MainModule 下。
  • 其中 module 文件夹存放自定义的 Module,或者说希望独立成模块但是还没有完全独立出来的。其中目录结构和这个项目目录结构类似
  • boot 文件夹是项目启动代码的时候执行的,这部分在 Nest.js 当中没有给出。我这里打算添加这个功能,但是还没有想好具体的实现形式,所以待定。
  • interface/enum 等数据随着对应的 service 导出。不另做说明。比如说 car.service.ts 除了可以导出 CarService 类以外,还可以导出 CarType enum。
  • dest 文件夹是编译之后的文件,可以直接输入 node dest/main.js 运行。
  • 命名规则
    • 所有的文件除了 main.ts 和类文件以外,都要添加类型后缀,比如说 user.model.ts car.controller.ts google.logic.ts。但是比如说只是一个 Car 类,那么可以直接命名成 car.ts
    • 不允许通过 export default 导出数据。一方面是为了方便导入的时候保证命名的统一,另一方面可以随时导出 interface/enum 等内容。
    • 所有的测试文件后缀名都以 .spec.ts.test.ts 结尾。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|-- dest
|--- ...
|-- src
|-- config
|-- controller
|-- model
|-- service
|-- logic
|-- component
|-- boot
|-- module
|-- module-name
|-- config
|-- index.ts
|-- module-name.module.ts
|-- main.ts
|-- main.module.ts

配置管理

我目前初步的想法是通过提供一个 ConfigModule 暴露出一个 ConfigService 来提供配置的获取和查看。

在某些情况下,可能需要多级配置,模块级别的配置,应用级别的配置。那么 ConfigService 可以在获取配置的时候自动合并这些规则。

进程管理

现在已经是 18 年了,不用 Docker 你真的对得起自己么?很明显是对不起的。所以进程管理这一块,我们就交给 Docker 来处理。包括启动、停止、重启、日志等,都交给 Docker。

于是启动命令就可以简化成 node dest/main.js 即可。

那么你可能会想到,如果一个 Docker 环境给你分配了两个 u,那岂不是会浪费一个 u。理论上是的,那么你就可以通过 pm2 啊啥的自己去管理吧,哈哈哈,不管。

Iron.js

说了这么多,把上面的内容都沉淀下来,我得要给他取个名字,于是我就取成了 Iron。为啥叫 Iron 呢?因为 Iron Man。那为啥因为 Iron Man 呢?因为他制作的盔甲可以自由拆分,自动拼合。非常适合我们这个项目的形态。

不过这个项目什么时候能沉淀下来,看我心情了。不过定个时间线吧,就在 4 月底,争取搞定。

因为这里面最大的问题就是配置的问题,需要深入依赖注入,所以会麻烦一些。但是其他的方面更多的只是一种约束吧。

这就是我用 Nest.js 一周以来的心得。暂时就想到这么多,更多的内容等我后面再分析吧。

写完睡觉,答应女票了,啦啦啦~

注释和共享

  • 第 1 页 共 1 页

XGHeaven

一个弱弱的码农


杭州电子科技大学学生一枚


Weifang Shandong, China