跳到主要内容

Nest 中的设计思想

一、 MVC 架构概述

Nest 属于 MVC(Model-View-Controller,模型-视图-控制器)架构体系,实际上,大多数后端框架都是基于这一架构设计的。

在 MVC 架构中:

  • Model 层负责业务逻辑处理,包括数据的获取、存储、验证以及数据库操作。
  • Controller 层通常用于处理用户的输入,调度 Service 服务,以及进行 API 的路由管理。
  • View 层在传统的服务端渲染中,可能使用如 ejs、hbs 等模板引擎。在前后端分离的体系中,通常指的是客户端框架(如 Vue 或 React)负责的部分。

当一个 HTTP 请求到达服务器时,它首先会被 Controller 层接收。Controller 层会根据请求调用 Model 层中的相应模块来处理业务逻辑,并将处理结果返回给 View 层以进行展示。

二、AOP 思想

AOP(面向切面编程,Aspect-Oriented Programming)是一种编程范式,旨在通过分离关注点(cross-cutting concerns)来提高代码的模块化。它的核心思想是将不同功能(如日志记录、事务管理、权限检查等)从业务逻辑中分离出来,独立地进行管理和维护。这样,代码的可读性、可维护性和复用性都会得到提高。

在传统的面向对象编程(OOP)中,程序的关注点(功能)通常会混杂在一起,导致代码难以扩展和维护。而AOP通过引入“切面”(Aspect)这一概念,使得这些功能(如日志、性能监控、错误处理等)能够在不修改原有业务逻辑代码的情况下进行统一处理。

2.1 AOP的主要概念:

  1. 切面(Aspect):切面是关注点的模块化,它定义了要在程序中何时、如何执行某些代码。比如,日志、事务等通常会是切面。

  2. 连接点(Join Point):程序执行过程中可以插入切面的点,比如方法调用、方法执行前后等。AOP会在这些连接点上插入增强代码。

  3. 通知(Advice):增强的代码,指在连接点执行的额外操作。通知有不同的类型:

    • 前置通知(Before):在目标方法执行之前执行。
    • 后置通知(After):在目标方法执行之后执行。
    • 环绕通知(Around):在目标方法执行前后都可以控制。
    • 异常通知(AfterThrowing):目标方法抛出异常时执行。
  4. 切入点(Pointcut):用于定义在哪些连接点(通常是方法调用)上执行通知。通过表达式来指定切入点。

  5. 织入(Weaving):将切面应用到目标对象的过程。织入可以发生在编译时、类加载时或者运行时。

2.2 AOP的优势:

  • 代码解耦:将横切关注点(例如日志、权限验证)与核心业务逻辑分离,降低了耦合度。
  • 增强功能:可以在不修改原有业务代码的情况下增加额外的功能。
  • 可维护性高:分离了不同的功能模块,使代码更容易维护。

2.3 AOP在实际中的应用:

  • 日志记录:每次方法调用前后自动记录日志。
  • 事务管理:自动在方法执行前开启事务,执行后提交或回滚。
  • 权限检查:在方法执行前自动检查权限。
  • 性能监控:记录方法的执行时间,分析性能瓶颈。

例如,Spring框架中的AOP就是非常典型的应用,它允许开发者在不修改业务逻辑代码的情况下,添加事务管理、日志记录等功能。

2.4 AOP在Nest中的应用

2.4.1 中间件

Nest的中间件默认是基于 Express 的。

中间件可以在路由处理程序之前或之后插入执行任务,分为全局中间件和局部中间件。以http请求为例。

全局中间件通过 use 方法调用。所有进入应用的请求都会经过全局中间件,通常用于执行日志统计、监控、安全性处理等任务。

async function bootstrap() {
const app = await NestFactory.create(AppModule);
// 全局中间件
app.use((new LoggerMiddleware()).use);
await app.listen(80);
}
void bootstrap();

局部中间件通常应用于特定的控制器或单个路由上,以实现更系粒度的逻辑控制。

export class UserModule implements NestModule {
configure(consumer: MiddlewareConsumer) {
// 针对此模块的所有路由绑定中间件
// consumer.apply(LoggerMiddleware).forRoutes('*');
// 指定中间件的路由和请求方式
consumer.apply(LoggerMiddleware).forRoutes({
path: '/user',
method: RequestMethod.GET,
});
}
}

2.4.2 守卫