NestJS 探索与实践

共 4988字,需浏览 10分钟

 ·

2024-04-11 12:13

3e7049dcebb262092dce173cc58e5645.webp

前段时间,我司前端基建团队上线了重定向管理系统——它提供二维码、短链接的创建和维护服务。

这是由前端团队全栈开发的项目:前端使用 React,后端使用 Nestjs 开发。我们将开发过程中得到的一些实践经验和思考,借着这个机会想和大家共同探讨一下。也希望通过这次分享,让大家对 NestJS 有一个初步的了解。

主题

这次分享主要包含4个内容

  • 什么是 nestJS?

  • 应用场景和能力,能解决什么问题?

  • 为什么选择它,有什么优势?

  • 如何上手?

What

da7dd9b4d8530f59565271bea521ab9d.webp 我们先回答第一个问题:什么是 nestJS。图中是官网的一段定义,大意是:

Nest 是一个用于构高效,可扩展的 Node.js 服务端应用程序的框架…… 它使用渐进式 JavaScript,内置并完全支持 TypeScript

从定义中,我们找出几个关键字来看看:

  • 基于 Node:对前端友好

  • 服务端应用框架:主要用于服务端接口开发

  • 高效,可扩展:体现在 Nest 各个功能模块之间的架构是解耦的、容易进行组合的

  • 渐进式:不需要一开始掌握它的全部功能特性,后续可根据业务需要逐步增加功能。

  • Nest 由 TS 开发,完全支持 TS

简单来说,Nest 是一个具有诸多特性的 Node 服务端框架。

When

第二个问题:有哪些应用场景和能力?能解决什么问题?

  1. 首先是最基础的,做服务端开发;

  2. 其次,对服务端功能的扩展,比如:安全、鉴权、队列、日志等

  3. 技术架构级支持:微服务,序列化等等

以上这些场景,官方都提供成熟的解决方案,对技术选型也是一种可靠的保障。

Why

说完 Nest 的应用场景,我们再来谈谈:为什么选择 Nest?我们简单回顾一下 Node Web 框架的发展历程,我这里将它们分为 起步和规模化 两个阶段:

82fafabb1e45a3571860b9f1e7a7a8de.webp

起步阶段 从 09 年 Node.js诞生开始,紧接着出现 Express,Koa。它们主打轻量、极简的框架

  • 它们开发风格自由开放,导致的结果是:大家都有自己的一套开发方式(不同的分层,项目结构,文件命名)。

  • 框架功能过于专一甚至简单,团队项目很少直接使用它们开发;它们的特点好像 web 中的 JQuery,依然强大,但已经不能满足复杂的开发需求

在这样的背景下,诞生了 Egg 和 Nest——主打企业级应用和团队协作,开箱即用

Nest 与 Egg 对比

Express 和 Koa 功能比较基础,Nest 与 Egg 是我们主要考虑的两种方案,我们通过以下几个方面来进行比较:

1c209db7eb0f7e1d7662525330421189.webp

  • 社区生态:Nest 社区非常繁荣,由官方提供解决方案;Egg 拥有插件市集,但插件质量参差不齐

  • 关注度,Nest 在 GitHub 上有 4.2 万 star,Egg 是 1.8 万(截止2022年1月);

  • 项目更新频率:Nest 高一些,github issue 反馈及时,使用体验更好

  • 架构设计:代码组织方式更合理——项目结构按照功能模块划分;装饰器语法更加优雅;基于依赖注入实现代码低耦合;Egg 提倡约定大于配置,缺乏一定的灵活性

  • Nest 原生支持 TS

  • 上手难度:Nest 概念较多,难度稍高一点

  • 维护成本,Nest 有一套标准化的开发流程,长期来看,利于保持项目的统一性

综合以上几个方面,我们认为:Nest 设计更加合理,也更适合我们团队

去年 12月, 阿里的 D2 大会上,也推出新的 Node Web 框架:MidWay。整体设计和 Nest 比较类似:支持装饰器,基于 DI 设计,支持 TS —— Egg.js 似乎完成了他的历史使命,将接力棒交到了 Midway 的手中

How

接下来我们从代码层面,介绍如何上手 Nest

脚手架

030a6307fb11b2b5718a57e0bbb7eeb1.webp

  • Nest 官方提供了脚手架,可通过 npm 全局安装,使用方法与 Vue client 大同小异,开发体验是比较不错的

  • 使用 Nest CLI 建立新项目非常简单,通过 nest new xxx 一键创建,会生成样板代码、安装依赖

  • 使用命令 npm start:dev 启动应用程序,监听入站 HTTP 请求。

目录结构

初始化的项目包含一些样板文件,主要看一下 src 目录,里面包含了几个核心文件

982649255476696cd9d4d967af10cef3.webp

main.ts

main.ts 是程序的入口文件,它包含一个异步函数:负责创建 app 实例,启动、监听服务器。内部引入了 app.module —— 模块文件,这是 Nest 一个核心概念

基础概念

module

  • 中文名叫模块,在 Nest 中是可运行功能的最小单元。

8afca0edfb78ae8b017462a9f7b46c3d.webp

  • 举个例子,我们的应用中有 用户管理,订单管理 等多个功能,每个功能都可独立成一个模块,下面这幅图,虚线表示服务端应用,其中用户管理功能是 UserModule,订单管理是 OrderModule,应用则是由许多的模块 moudule 组成……在他们前面还有一个 AppModule,是整个应用根模块

d2331c462f5f502b4a780a7b1ef3a8c7.webp

  • 整个模块的结构类似前端 SPA 应用:前端应用 app 挂载到 root 根节点上,pages 目录下的文件映射为不同的页面组件

  • 为了创建一个基本的模块,我们将使用  和 装饰器

装饰器

bd77f52a23a4e6e1c70b7c35431039dd.webp

  • 装饰器是一种特殊的语法,它用来修改或增强类、函数、对象等,写法是 @expression, 在 TS 中率先支持这种语法

  • expression 表达式求值后必须为一个函数,它会在运行时被调用;被装饰的主体做为参数传入。

  • 代码中在 AppModule 这个类前面,添加了一个 @Module() 装饰器,作用是返回一个模块类,并提供模块的元数据(上下文及依赖)。

app.module.ts 中引入了 Controller 和 Service ,我顺着代码的依赖关系,来介绍一下 Controller

controller

f40f78cfb68c5ad5e168797eae81b47b.webp

  • 控制器,负责处理传入的请求和返回的响应

  • 它像一个处理器(dispatcher),接收 HTTP 请求,然后通过 路由分发 机制,调用命中的方法

  • @Get 这个请求类型装饰器,可以接受一个路由地址。如果路由命中,则会调用 getHello 方法;这里为空,会匹配根路径

  • 控制器重要负责处理 HTTP 请求,而将更复杂的任务委托给提供者,即 Service(providers), Service 是什么?

Service

44126f9034740daad2c62db40f7a345c.webp

  • 在 Nest 中,服务 是一种常见的 提供者(Provider) ,它负责一些基础、公用的方法,比如处理业务逻辑,与数据库交互……提供者通过依赖注入的方式,被注入到控制器(Controller)中,这些方法由 Controller 调用。

刚刚介绍了Nest 中最核心的三个概念

  • 模块是 Nest 中的最小单元,许许多多的单元组成了应用;也可以看作一个容器,如下图,每个模块中包含了控制器和提供者

0b90abc0829861ae84087fdf47cc0441.webp

  • 控制器处理 HTTP 请求,分发路由;调用提供者的方法;

  • 提供者中定义了基础/公共的逻辑

需要注意的是:

  • 在开发中为了避免把逻辑分散在各个文件中,要注意区分控制器和提供者边界,

即:

  • 在 Controller 中主要处理请求、分发路由;注意,一个控制器中可以调用多个提供者;

  • 在提供者中处理基础业务逻辑

如下图代码…这样进行功能分层,能够保障代码的清晰和统一

ed59cab8fb4139e358fa69c8d48f0576.webp

除此之外,Nest 还有中间件(在路由程序前后执行),异常过滤器(处理程序抛出的异常错误),守卫(权限校验)……等概念,用于开发一些常见的功能。但我没有把它们归类到 Nest 核心概念中,大家有兴趣下来可以再了解。

依赖注入

前面经常讲到 Nest 是可扩展,低耦合的,那么它是怎么实现的呢?答案是:依赖注入

依赖注入(Dependecy Injection)是实现低耦合的一种方式(也可以叫设计模式),它将对象创建和对象消耗分开。所需的依赖关系不是在内部创建,而是通过外部透明地传递

这里有一段代码,包含两个类:第一个是 引擎 Engine,第二个是 汽车 Car

b4433967fa5b05e1a81436726da253cf.webp

  • Engine 有一个 cylinders 属性,表示引擎类型,我先给他一个固定属性为内燃机引擎

  • Car 构造器中通过 new 实例化了一个引擎,在 drive 方法中使用了引擎的类属性,返回字符串:这是一个内燃机引擎汽车

  • 有一天,我打算改动 Engine,不再设置固定类型,而是通过构造器动态传入;那么,Car 也需要改动构造函数中的代码。这样一来就不符合低耦合的标准。

9a1f4d5a61d1744fc69ba386cf043201.webp 那我们使用依赖注入的方式,该怎么处理?

162f51c1030987a619aa8266dcd8610f.webp

  • 在汽车中,通过构造函数直接传递 Engine 对象,而不是在内部创建

  • 在外部 main 方法中,实例化 Car 时,也会实例化 Engine 对象,并把 Engine 传入到 Car 中;这个过程叫做注入;注意,这里是把 Engine 传入到 Car 中;

  • 这样修改后,无论 Engine 的逻辑怎样改变,都不会影响到 Car 的代码

我们再回头看一下,Nest 中是怎么应用依赖注入的?

  • 回到 Module 文件,@Module 装饰器中会声明 controllers 和 providers@Module 装饰器实际是替代了 main 函数。会实例化 Controller 和 Provider,并将 Provider 注入到 Controller 中

18998b5554045b515c3b9d6a96fdfa7f.webp

  • 于是在 Controller 中可以通过 this 调用 service。

470b39915c8346877c3abc0d272ef03c.webp

Nest 中是将 provider 注入到 controller,因此 Provider 的装饰器名字叫做 Injectable

这就是在 Nest 从框架层面,通过依赖注入实现模块的低耦合,从而提高了代码的可扩展性。

Summary

通过上面对 Nest 的讲解,简单总结一下:

第一:我们主要从四个方面来介绍了 Nest:What,When,Why,How

第二:Nest 存在一些缺点:它的概念较多;设计模式与平常不一样;国内开发者目前比较少;

总体评价 Nest.js,我觉得它是一个「优雅的,标准化,可扩展的」 Node 框架。在使用过程中除了知道这玩意怎么用,也能慢慢学习到后端开发模式,Nest 在的设计上的优点也是值得我们学习和探索的。


作者:samwangdd

链接:https://juejin.cn/post/7054931414478749710

来源:稀土掘金

浏览 24
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报