Nest.js 是如何實(shí)現(xiàn) AOP 架構(gòu)的?
Nest.js 是一個(gè) Node.js 的后端框架,它對(duì) express 等 http 平臺(tái)做了一層封裝,解決了架構(gòu)問(wèn)題。它提供了 express 沒(méi)有的 MVC、IOC、AOP 等架構(gòu)特性,使得代碼更容易維護(hù)、擴(kuò)展。
這里的 MVC、IOC、AOP 都是啥意思呢?我們分別看一下:
MVC、IOC
MVC 是 Model View Controller 的簡(jiǎn)寫(xiě)。MVC 架構(gòu)下,請(qǐng)求會(huì)先發(fā)送給 Controller,由它調(diào)度 Model 層的 Service 來(lái)完成業(yè)務(wù)邏輯,然后返回對(duì)應(yīng)的 View。
Nest.js 提供了 @Controller 裝飾器用來(lái)聲明 Controller:
而 Service 會(huì)用 @Injectable 裝飾器來(lái)聲明:
通過(guò) @Controller、@Injectable 裝飾器聲明的 class 會(huì)被 Nest.js 掃描,創(chuàng)建對(duì)應(yīng)的對(duì)象并加到一個(gè)容器里,這些所有的對(duì)象會(huì)根據(jù)構(gòu)造器里聲明的依賴(lài)自動(dòng)注入,也就是 DI(dependency inject),這種思想叫做 IOC(Inverse Of Control)。
IOC 架構(gòu)的好處是不需要手動(dòng)創(chuàng)建對(duì)象和根據(jù)依賴(lài)關(guān)系傳入不同對(duì)象的構(gòu)造器中,一切都是自動(dòng)掃描并創(chuàng)建、注入的。
此外,Nest.js 還提供了 AOP (Aspect Oriented Programming)的能力,也就是面向切面編程的能力:
AOP
AOP 是什么意思呢?什么是面向切面編程呢?
一個(gè)請(qǐng)求過(guò)來(lái),可能會(huì)經(jīng)過(guò) Controller(控制器)、Service(服務(wù))、Repository(數(shù)據(jù)庫(kù)訪(fǎng)問(wèn)) 的邏輯:
如果想在這個(gè)調(diào)用鏈路里加入一些通用邏輯該怎么加呢?比如日志記錄、權(quán)限控制、異常處理等。
容易想到的是直接改造 Controller 層代碼,加入這段邏輯。這樣可以,但是不優(yōu)雅,因?yàn)檫@些通用的邏輯侵入到了業(yè)務(wù)邏輯里面。能不能透明的給這些業(yè)務(wù)邏輯加上日志、權(quán)限等處理呢?
那是不是可以在調(diào)用 Controller 之前和之后加入一個(gè)執(zhí)行通用邏輯的階段呢?
比如這樣:
這樣的橫向擴(kuò)展點(diǎn)就叫做切面,這種透明的加入一些切面邏輯的編程方式就叫做 AOP (面向切面編程)。
AOP 的好處是可以把一些通用邏輯分離到切面中,保持業(yè)務(wù)邏輯的存粹性,這樣切面邏輯可以復(fù)用,還可以動(dòng)態(tài)的增刪
其實(shí) Express 的中間件的洋蔥模型也是一種 AOP 的實(shí)現(xiàn),因?yàn)槟憧梢酝该鞯脑谕饷姘粚?,加入一些邏輯,?nèi)層感知不到。
而 Nest.js 實(shí)現(xiàn) AOP 的方式更多,一共有五種,包括 Middleware、Guard、Pipe、Inteceptor、ExceptionFilter:
中間件 Middleware
Nest.js 基于 Express 自然也可以使用中間件,但是做了進(jìn)一步的細(xì)分,分為了全局中間件和路由中間件:
全局中間件就是 Express 的那種中間件,在請(qǐng)求之前和之后加入一些處理邏輯,每個(gè)請(qǐng)求都會(huì)走到這里:
路由中間件則是針對(duì)某個(gè)路由來(lái)說(shuō)的,范圍更小一些:
這個(gè)是直接繼承了 Express 的概念,比較容易理解。
再來(lái)看一些 Nest.js 擴(kuò)展的概念,比如 Guard:
Guard
Guard 是路由守衛(wèi)的意思,可以用于在調(diào)用某個(gè) Controller 之前判斷權(quán)限,返回 true 或者 flase 來(lái)決定是否放行:
創(chuàng)建 Guard 的方式是這樣的:
Guard 要實(shí)現(xiàn) CanActivate 接口,實(shí)現(xiàn) canActive 方法,可以從 context 拿到請(qǐng)求的信息,然后做一些權(quán)限驗(yàn)證等處理之后返回 true 或者 false。
通過(guò) @Injectable 裝飾器加到 IOC 容器中,然后就可以在某個(gè) Controller 啟用了:
Controller 本身不需要做啥修改,卻透明的加上了權(quán)限判斷的邏輯,這就是 AOP 架構(gòu)的好處。
而且,就像 Middleware 支持全局級(jí)別和路由級(jí)別一樣,Guard 也可以全局啟用:
Guard 可以抽離路由的訪(fǎng)問(wèn)控制邏輯,但是不能對(duì)請(qǐng)求、響應(yīng)做修改,這種邏輯可以使用 Interceptor:
Interceptor
Interceptor 是攔截器的意思,可以在目標(biāo) Controller 方法前后加入一些邏輯:
創(chuàng)建 Inteceptor 的方式是這樣的:
Interceptor 要實(shí)現(xiàn) NestInterceptor 接口,實(shí)現(xiàn) intercept 方法,調(diào)用 next.handle() 就會(huì)調(diào)用目標(biāo) Controller,可以在之前和之后加入一些處理邏輯。
Controller 之前之后的處理邏輯可能是異步的。Nest.js 里通過(guò) rxjs 來(lái)組織它們,所以可以使用 rxjs 的各種 operator。
Interceptor 支持每個(gè)路由單獨(dú)啟用,只作用于某個(gè) controller,也同樣支持全局啟用,作用于全部 controller:
除了路由的權(quán)限控制、目標(biāo) Controller 之前之后的處理這些都是通用邏輯外,對(duì)參數(shù)的處理也是一個(gè)通用的邏輯,所以 Nest.js 也抽出了對(duì)應(yīng)的切面,也就是 Pipe:
Pipe
Pipe 是管道的意思,用來(lái)對(duì)參數(shù)做一些驗(yàn)證和轉(zhuǎn)換:
創(chuàng)建 Pipe 的方式是這樣的:
Pipe 要實(shí)現(xiàn) PipeTransform 接口,實(shí)現(xiàn) transform 方法,里面可以對(duì)傳入的參數(shù)值 value 做參數(shù)驗(yàn)證,比如格式、類(lèi)型是否正確,不正確就拋出異常。也可以做轉(zhuǎn)換,返回轉(zhuǎn)換后的值。
內(nèi)置的有 8 個(gè) Pipe,從名字就能看出它們的意思:
- ValidationPipe。
- ParseIntPipe。
- ParseBoolPipe。
- ParseArrayPipe。
- ParseUUIDPipe。
- DefaultValuePipe。
- ParseEnumPipe。
- ParseFloatPipe。
同樣,Pipe 可以只對(duì)某個(gè)路由生效,也可以對(duì)每個(gè)路由都生效:
不管是 Pipe、Guard、Interceptor 還是最終調(diào)用的 Controller,過(guò)程中都可以?huà)伋鲆恍┊惓?,如何?duì)某種異常做出某種響應(yīng)呢?
這種異常到響應(yīng)的映射也是一種通用邏輯,Nest.js 提供了 ExceptionFilter 來(lái)支持:
ExceptionFilter
ExceptionFilter 可以對(duì)拋出的異常做處理,返回對(duì)應(yīng)的響應(yīng):
創(chuàng)建 ExceptionFilter的形式是這樣的:
首先要實(shí)現(xiàn) ExceptionFilter 接口,實(shí)現(xiàn) catch 方法,就可以攔截異常了,但是要攔截什么異常還需要用 @Catch 裝飾器來(lái)聲明,攔截了異常之后,可以異常對(duì)應(yīng)的響應(yīng),給用戶(hù)更友好的提示。
當(dāng)然,也不是所有的異常都會(huì)處理,只有繼承 HttpException 的異常才會(huì)被 ExceptionFilter 處理,Nest.js 內(nèi)置了很多 HttpException 的子類(lèi):
- BadRequestException。
- UnauthorizedException。
- NotFoundException。
- ForbiddenException。
- NotAcceptableException。
- RequestTimeoutException。
- ConflictException。
- GoneException。
- PayloadTooLargeException。
- UnsupportedMediaTypeException。
- UnprocessableException。
- InternalServerErrorException。
- NotImplementedException。
- BadGatewayException。
- ServiceUnavailableException。
- GatewayTimeoutException。
當(dāng)然,也可以自己擴(kuò)展:
Nest.js 通過(guò)這樣的方式實(shí)現(xiàn)了異常到響應(yīng)的對(duì)應(yīng)關(guān)系,代碼里只要拋出不同的 HttpException,就會(huì)返回對(duì)應(yīng)的響應(yīng),很方便。
同樣,ExceptionFilter 也可以選擇全局生效或者某個(gè)路由生效:
某個(gè)路由:
全局:
我們了解了 Nest.js 提供的 AOP 的機(jī)制,但它們的順序關(guān)系是怎樣的呢?
幾種 AOP 機(jī)制的順序
Middleware、Guard、Pipe、Interceptor、ExceptionFilter 都可以透明的添加某種處理邏輯到某個(gè)路由或者全部路由,這就是 AOP 的好處。
但是它們之間的順序關(guān)系是什么呢?
調(diào)用關(guān)系這個(gè)得看源碼了。
對(duì)應(yīng)的源碼是這樣的:
很明顯,進(jìn)入這個(gè)路由的時(shí)候,會(huì)先調(diào)用 Guard,判斷是否有權(quán)限等,如果沒(méi)有權(quán)限,這里就拋異常了:
拋出的 HttpException 會(huì)被 ExceptionFilter 處理。
如果有權(quán)限,就會(huì)調(diào)用到攔截器,攔截器組織了一個(gè)鏈條,一個(gè)個(gè)的調(diào)用,最后會(huì)調(diào)用的 controller 的方法:
調(diào)用 controller 方法之前,會(huì)使用 pipe 對(duì)參數(shù)做處理:
會(huì)對(duì)每個(gè)參數(shù)做轉(zhuǎn)換:
ExceptionFilter 的調(diào)用時(shí)機(jī)很容易想到,就是在響應(yīng)之前對(duì)異常做一次處理。
而 Middleware 是 express 中的概念,Nest.js 只是繼承了下,那個(gè)是在最外層被調(diào)用。
這就是這幾種 AOP 機(jī)制的調(diào)用順序。把這些理清楚,就算是對(duì) Nest.js 有很好的掌握了。
總結(jié)
Nest.js 基于 express 這種 http 平臺(tái)做了一層封裝,應(yīng)用了 MVC、IOC、AOP 等架構(gòu)思想。
MVC 就是 Model、View Controller 的劃分,請(qǐng)求先經(jīng)過(guò) Controller,然后調(diào)用 Model 層的 Service、Repository 完成業(yè)務(wù)邏輯,最后返回對(duì)應(yīng)的 View。
IOC 是指 Nest.js 會(huì)自動(dòng)掃描帶有 @Controller、@Injectable 裝飾器的類(lèi),創(chuàng)建它們的對(duì)象,并根據(jù)依賴(lài)關(guān)系自動(dòng)注入它依賴(lài)的對(duì)象,免去了手動(dòng)創(chuàng)建和組裝對(duì)象的麻煩。
AOP 則是把通用邏輯抽離出來(lái),通過(guò)切面的方式添加到某個(gè)地方,可以復(fù)用和動(dòng)態(tài)增刪切面邏輯。
Nest.js 的 Middleware、Guard、Interceptor、Pipe、ExceptionFileter 都是 AOP 思想的實(shí)現(xiàn),只不過(guò)是不同位置的切面,它們都可以靈活的作用在某個(gè)路由或者全部路由,這就是 AOP 的優(yōu)勢(shì)。
我們通過(guò)源碼來(lái)看了它們的調(diào)用順序,Middleware 是 Express 的概念,在最外層,到了某個(gè)路由之后,會(huì)先調(diào)用 Guard,Guard 用于判斷路由有沒(méi)有權(quán)限訪(fǎng)問(wèn),然后會(huì)調(diào)用 Interceptor,對(duì) Contoller 前后擴(kuò)展一些邏輯,在到達(dá)目標(biāo) Controller 之前,還會(huì)調(diào)用 Pipe 來(lái)對(duì)參數(shù)做驗(yàn)證和轉(zhuǎn)換。所有的 HttpException 的異常都會(huì)被 ExceptionFilter 處理,返回不同的響應(yīng)。
Nest.js 就是通過(guò)這種 AOP 的架構(gòu)方式,實(shí)現(xiàn)了松耦合、易于維護(hù)和擴(kuò)展的架構(gòu)。
AOP 架構(gòu)的好處,你感受到了么?