当前位置: 首页 > 科技观察

Nest.js是如何实现AOP架构的?

时间:2023-03-16 17:06:55 科技观察

Nest.js是一个Node.js后端框架。对express等http平台进行封装,解决架构问题。它提供了express不具备的MVC、IOC、AOP等架构特性,使代码更易于维护和扩展。MVC、IOC、AOP在这里是什么意思?让我们分别来看一下:MVC和IOCMVC是ModelViewController的简写。在MVC架构下,请求会先发送给Controller,由Controller调度Model层的Service完成业务逻辑,然后返回对应的View。Nest.js提供了@Controller装饰器来声明Controller:Service会使用@Injectable装饰器来声明:@Controller和@Injectable装饰器声明的类会被Nest.js扫描,创建相应的对象并添加到一个在容器中,所有这些对象都会根据构造函数中声明的依赖自动注入,这就是DI(dependencyinject)。这个想法叫做IOC(InverseOfControl)。IOC架构的好处是不需要手动创建对象,然后根据依赖关系传入不同对象的构造函数。一切都会自动扫描、创建和注入。另外,Nest.js还提供了AOP(AspectOrientedProgramming)的能力,即面向切面编程的能力:AOPAOP是什么意思?什么是面向方面的编程?一个request可能会经过Controller(控制器)、Service(服务)、Repository(数据库访问)逻辑:如果要在这个调用环节中加入一些通用的逻辑,应该怎么加入呢?比如日志记录,权限控制,异常处理等。很容易想到直接修改Controller层代码,加入这个逻辑。这还好,但不够优雅,因为这些公共逻辑侵入了业务逻辑。能不能透明的给这些业务逻辑加上日志、权限等?是否可以在调用Controller前后增加一个执行通用逻辑的阶段?例如:这样的横向扩展点,称为切面。透明地加入一些切面逻辑的编程方式称为AOP(AspectOrientedProgramming)。AOP的好处是可以将一些通用的逻辑分离成切面,同时保持业务逻辑不变,这样切面的逻辑就可以被复用,动态增删改查。其实Express中间件的洋葱模型也是AOP的一种实现,因为你可以在外面透明的包裹一层,加上一些逻辑,让内层无法感知。Nest.js有更多的方式来实现AOP。一共有五种,中间件、Guard、Pipe、Inteceptor、ExceptionFilter:中间件MiddlewareNest.js也可以使用基于Express的中间件,但是又进一步细分为全局中间件和路由中间件:全局中间件是那种Express的中间件,在请求前后添加一些处理逻辑,每个请求都会到这里:路由中间件是针对某个路由的,范围更小:这是直接继承了Express的概念,更容易理解.看一些Nest.js的扩展概念,比如Guard:GuardGuard的意思是路由守卫,可以用来在调用Controller之前判断权限,返回true或者flase来决定是否释放:创建Guard的方式是这样的:Guard实现了CanActivate接口和canActive方法,可以从context中获取请求的信息,然后在做一些权限验证等处理后返回true或false。通过@Injectable装饰器将其添加到IOC容器中,然后在某个Controller中启用:Controller本身不需要修改,只是透明添加了权限判断的逻辑,这就是AOP架构的好处。而且,就像Middleware支持全局级别和路由级别一样,Guard也可以全局启用:Guard可以提取路由的访问控制逻辑,但不能修改请求和响应。这个逻辑可以使用Interceptor:InterceptorInterceptor是拦截器的意思,可以在目标Controller方法前后添加一些逻辑:创建Inteceptor的方式是这样的:Interceptor需要实现NestInterceptor接口,实现intercept方法,以及调用next.handle()来调用目标Controller。您可以在逻辑前后添加一些处理。Controller前后的处理逻辑可能是异步的。Nest.js通过rxjs来组织它们,所以可以使用rxjs的各种操作符。Interceptor支持每条路由单独开启,只针对某个controller,也支持全局激活,针对所有controller:除了路由权限控制和目标Controller前后的处理都是通用逻辑外,处理parameters的也是一个通用的逻辑,所以Nest.js也抽取了相应的aspect,即Pipe:PipePipe的意思是pipeline,用来校验和转换参数:Pipe的创建方式如下:Pipe必须实现PipeTransform接口,实现transform方法,可以对传入的参数值value进行参数校验,比如格式和类型是否正确,不正确会抛出异常。也可以进行转换,返回转换后的值。内置了8个Pipes,从名字就可以看出它们的含义:ValidationPipe。ParseIntPipe。解析布尔管道。解析数组管道。解析UUID管道。默认值管道。解析枚举管道。解析浮管。同样的,Pipe只能对某条路由生效,也可以对每条路由都生效:不管是Pipe、Guard、Interceptor还是最终调用的Controller,过程中都会抛出一些异常,如何让某些异常发生某些例外情况?什么样的反应?这种异常到响应的映射也是一种常见的逻辑。Nest.js提供了ExceptionFilter来支持:ExceptionFilterExceptionFilter可以处理抛出的异常并返回相应的响应:创建ExceptionFilter的形式如下:首先要实现ExceptionFilter接口和catch方法,可以拦截异常,但是要处理异常intercepted需要用@Catch装饰器声明。异常被拦截后,可以对异常进行响应,给用户更友好的提示。当然,并不是所有的异常都会被处理。只有继承HttpException的异常才会被ExceptionFilter处理。Nest.js内置了很多HttpException的子类:BadRequestException。未经授权的异常。NotFoundException。禁止异常。不可接受的异常。请求超时异常。冲突异常。消失异常。负载过大异常。不支持的媒体类型异常。无法处理的异常。内部服务器错误异常。未实现异常。BadGatewayException。服务不可用异常。网关超时异常。当然你也可以自己扩展一下:Nest.js就是这样实现异常和响应的对应的。只要在代码中抛出不同的HttpException,就会返回相应的response,非常方便。同样,ExceptionFilter也可以选择全局生效或者某路由生效:某路由:global:我们理解Nest.js提供的AOP机制,但是它们的顺序关系是怎样的呢?Middleware、Guard、Pipe、Interceptor、ExceptionFilter这几种AOP机制的顺序,可以透明的给一个路由或者所有路由添加某种处理逻辑,这就是AOP的好处。但是它们之间的顺序关系是怎样的呢?调用关系取决于源代码。对应的源码如下:显然,进入这条路由时,会先调用Guard判断是否有权限等,如果没有权限,这里会抛出异常:抛出的HttpException会被ExceptionFilter处理.如果你有权限,你会调用拦截器。拦截器组织一个链条,一个一个调用,最后调用controller方法:在调用controller方法之前,会先用pipe处理参数:每个参数都会处理转换:ExceptionFilter的调用时机很容易想到,即在响应之前处理一次异常。中间件在express中是一个概念,Nest.js只是继承了它,在最外层调用。这就是这几种AOP机制的调用顺序。弄清楚这些事情,你就会很好地掌握Nest.js。总结Nest.js基于express的http平台做了一层封装,应用了MVC、IOC、AOP等架构思想。MVC是模型和视图控制器的划分。请求首先经过Controller,然后调用Model层的Service和Repository完成业务逻辑,最后返回对应的View。IOC是指Nest.js会自动扫描带有@Controller和@Injectable装饰器的类,创建它们的对象,并根据依赖关系自动注入它所依赖的对象,省去了手动创建和组装对象的麻烦。AOP是将通用逻辑抽取出来,通过切面方法添加到某个地方,可以复用和动态增删切面逻辑。Nest.js的Middleware、Guard、Interceptor、Pipe、ExceptionFileter都是AOP思想的实现,只是不同位置的方面而已。它们都可以灵活的应用于某个路由或者所有路由,这是AOP的优势。我们通过源代码查看了它们的调用顺序。中间件是Express的概念。在最外层,到达某条路线后,会最先召唤守卫。Guard用于判断路由是否有权限访问,然后会调用Interceptor向Contoller展开前后的一些逻辑,在到达目标Controller之前调用Pipe进行参数校验和转换。所有的HttpException异常都会被ExceptionFilter处理并返回不同的响应。Nest.js通过这种AOP架构,实现了一种松散耦合、易于维护和扩展的架构。你感受到AOP架构的好处了吗?