極課編程-Nestjs的設計思想和基本使用方法

參考文檔

最近已經(jīng)使用過一段時間的nestjs,讓人寫著有一種java spring的感覺,nestjs可以使用express的所有中間件效五,此外完美的支持typescript,與數(shù)據(jù)庫關(guān)系映射typeorm配合使用可以快速的編寫一個接口網(wǎng)關(guān)。本文會介紹一下作為一款企業(yè)級的node框架的特點和優(yōu)點下面。

從依賴注入(DI)談起
裝飾器和注解
nestjs的“洋蔥模型”
nestjs的特點總結(jié)

一、從依賴注入(DI)談起
(1)绩聘、angular中的依賴注入
??從angular1.x開始沥割,實現(xiàn)了依賴注入或者說控制反轉(zhuǎn)的模式耗啦,angular1.x中就有controller(控制器)、service(服務),模塊(module)机杜。筆者在早年間寫過一段時間的angular1.3,下面舉例來說明:

var myapp=angular.module('myapp',['ui.router']);
myapp.controller('test1',function($scope,$timeout){}
myapp.controller('test2',function($scope,$state){}

上面這個就是angular1.3中的一個依賴注入的例子帜讲,首先定義了模塊名為“myapp”的module, 接著在myapp這個模塊中定義controller控制器。將myapp模塊的控制權(quán)交給了myapp.controller函數(shù)椒拗。具體的依賴注入的流程圖如下所示:

myapp這個模塊如何定義似将,由于它的兩個控制器決定,此外在控制器中又依賴于scope蚀苛、timeout等服務在验。這樣就實現(xiàn)了依賴注入,或者說控制反轉(zhuǎn)堵未。

(2)腋舌、什么是依賴注入
??用一個例子來通俗的講講什么是依賴注入。

class Cat{
 
}
class Tiger{
 
}
class Zoo{
  constructor(){
     this.tiger = new Tiger();
     this.cat = new Cat();
  }
}
 

上述的例子中渗蟹,我們定義Zoo块饺,在其constructor的方法中進行對于Cat和Tiger的實例化,此時如果我們要為Zoo增加一個實例變量雌芽,比如去修改Zoo類本身授艰,比如我們現(xiàn)在想為Zoo類增加一個Fish類的實例變量:

class Fish{}
 
class Zoo{
  constructor(){
     this.tiger = new Tiger();
     this.cat = new Cat();
     this.fish = new Fish();
  }
}

此外如果我們要修改在Zoo中實例化時,傳入Tiger和Cat類的變量世落,也必須在Zoo類上修改淮腾。這種反反復復的修改會使得Zoo類并沒有通用性,使得Zoo類的功能需要反復測試岛心。

我們設想將實例化的過程以參數(shù)的形式傳遞給Zoo類:

class Zoo{
  constructor(options){
     this.options = options;
  }
}
var zoo = new Zoo({
  tiger: new Tiger(),
  cat: new Cat(),
  fish: new Fish()
})

我們將實力化的過程放入?yún)?shù)中来破,傳入給Zoo的構(gòu)造函數(shù)篮灼,這樣我們就不用在Zoo類中反復的去修改代碼忘古。這是一個簡單的介紹依賴注入的例子,更為完全使用依賴注入的可以為Zoo類增加靜態(tài)方法和靜態(tài)屬性:

class Zoo{
  static animals = [];
  constructor(options){
     this.options = options;
     this.init();
  }
  init(){
    let _this = this;
    animals.forEach(function(item){
      item.call(_this,options);
    })
  }
  static use(module){
     animals.push([...module])
  }
}
Zoo.use[Cat,Tiger,Fish];
var zoo = new Zoo(options);
 

上述我們用Zoo的靜態(tài)方法use往Zoo類中注入Cat诅诱、Tiger髓堪、Fish模塊,將Zoo的具體實現(xiàn)移交給了Cat和Tiger和Fish模塊,以及構(gòu)造函數(shù)中傳入的options參數(shù)娘荡。

(3)干旁、nestjs中的依賴注入
??在nestjs中也參考了angular中的依賴注入的思想,也是用module炮沐、controller和service争群。

@Module({
  imports:[otherModule],
  providers:[SaveService],
  controllers:[SaveController,SaveExtroController]
})
export class SaveModule {}

上面就是nestjs中如何定一個module,在imports屬性中可以注入其他模塊大年,在prividers注入相應的在控制器中需要用到的service换薄,在控制器中注入需要的controller玉雾。

二、裝飾器和注解
??在nestjs中轻要,完美的擁抱了typescript,特別是大量的使用裝飾器和注解复旬,對于裝飾器和注解的理解可以參考我的這篇文章:Typescript中的裝飾器和注解。我們來看使用了裝飾器和注解后冲泥,在nestjs中編寫業(yè)務代碼有多么的簡潔:

import { Controller, Get, Req, Res } from '@nestjs/common';
 
@Controller('cats')
 
export class CatsController {
  @Get()
  findAll(@Req() req,@Res() res) {
    return 'This action returns all cats';
  }
}

上述定義兩個一個處理url為“/cats”的控制器,對于這個路由的get方法驹碍,定義了findAll函數(shù)。當以get方法凡恍,請求/cats的時候志秃,就會主動的觸發(fā)findAll函數(shù)。

此外在findAll函數(shù)中,通過req和res參數(shù)嚼酝,在主題內(nèi)也可以直接使用請求request以及對于請求的響應response洽损。比如我們通過req上來獲取請求的參數(shù),以及通過res.send來返回請求結(jié)果革半。

三碑定、nestjs的“洋蔥模型”
??這里簡單講講在nestjs中是如何分層的,也就是說請求到達服務端后如何層層處理又官,直到響應請求并將結(jié)果返回客戶端延刘。

在nestjs中在service的基礎上,按處理的層次補充了中間件(middleware)六敬、異常處理(Exception filters)碘赖、管道(Pipes),守衛(wèi)(Guards),以及攔截器(interceptors)在請求到打真正的處理函數(shù)之間進行了層層的處理。

上圖中的邏輯就是分層處理的過程外构,經(jīng)過分層的處理請求才能到達服務端處理函數(shù)普泡,下面我們來介紹nestjs中的層層模型的具體作用。

(1)审编、middleware中間件
??在nestjs中的middle完全跟express的中間件一摸一樣撼班。不僅如此,我們還可以直接使用express中的中間件垒酬,比如在我的應用中需要處理core跨域:

import * as cors from 'cors';
async function bootstrap() {
  onst app = await NestFactory.create(/* 創(chuàng)建app的業(yè)務邏輯*/)
  app.use(cors({
    origin:'http://localhost:8080',
    credentials:true
  }));
  await app.listen(3000)
}
bootstrap();
 

在上述的代碼中我們可以直接通過app.use來使用core這個express中的中間件砰嘁。從而使得server端支持core跨域等。

初此之外勘究,跟nestjs的中間件也完全保留了express中的中間件的特點:

在中間件中接受response和request作為參數(shù)矮湘,并且可以修改請求對象request和結(jié)果返回對象response
可以結(jié)束對于請求的處理,直接將請求的結(jié)果返回口糕,也就是說可以在中間件中直接res.send等缅阳。
在該中間件處理完畢后,如果沒有將請求結(jié)果返回景描,那么可以通過next方法十办,將中間件傳遞給下一個中間件處理孤里。
在nestjs中,中間件跟express中完全一樣橘洞,除了可以復用express中間件外捌袜,在nestjs中針對某一個特定的路由來使用中間件也十分的方便:

class ApplicationModule implements NestModule {
  configure(consumer: MiddlewareConsumer) {
    consumer
      .apply(LoggerMiddleware)
      .forRoutes('cats');
  }
}

上面就是對于特定的路由url為/cats的時候,使用LoggerMiddleware中間件炸枣。

(2)虏等、Exception filters異常過濾器
??Exception filters異常過濾器可以捕獲在后端接受處理任何階段所跑出的異常,捕獲到異常后适肠,然后返回處理過的異常結(jié)果給客戶端(比如返回錯誤碼霍衫,錯誤提示信息等等)。

我們可以自定義一個異常過濾器侯养,并且在這個異常過濾器中可以指定需要捕獲哪些異常敦跌,并且對于這些異常應該返回什么結(jié)果等,舉例一個自定義過濾器用于捕獲HttpException異常的例子逛揩。

@Catch(HttpException)
export class HttpExceptionFilter implements ExceptionFilter {
  catch(exception: HttpException, host: ArgumentsHost) {
    const ctx = host.switchToHttp();
    const response = ctx.getResponse();
    const request = ctx.getRequest();
    const status = exception.getStatus();
 
    response
      .status(status)
      .json({
        statusCode: status,
        timestamp: new Date().toISOString(),
        path: request.url,
      });
  }
}

我們可以看到host是實現(xiàn)了ArgumentsHost接口的,在host中可以獲取運行環(huán)境中的信息柠傍,如果在http請求中那么可以獲取request和response,如果在socket中也可以獲取client和data信息辩稽。

同樣的惧笛,對于異常過濾器,我們可以指定在某一個模塊中使用逞泄,或者指定其在全局使用等患整。

(3)、Pipes管道
??Pipes一般用戶驗證請求中參數(shù)是否符合要求喷众,起到一個校驗參數(shù)的功能各谚。

比如我們對于一個請求中的某些參數(shù),需要校驗或者轉(zhuǎn)化參數(shù)的類型:

@Injectable()
export class ParseIntPipe implements PipeTransform<string, number> {
  transform(value: string, metadata: ArgumentMetadata): number {
    const val = parseInt(value, 10);
    if (isNaN(val)) {
      throw new BadRequestException('Validation failed');
    }
    return val;
  }
}
 

上述的ParseIntPipe就可以把參數(shù)轉(zhuǎn)化成十進制的整型數(shù)字到千。我們可以這樣使用:

@Get(':id')
async findOne(@Param('id', new ParseIntPipe()) id) {
 return await this.catsService.findOne(id);
}

對于get請求中的參數(shù)id昌渤,調(diào)用new ParseIntPipe方法來將id參數(shù)轉(zhuǎn)化成十進制的整數(shù)。

(4)父阻、Guards守衛(wèi)
??Guards守衛(wèi)愈涩,其作用就是決定一個請求是否應該被處理函數(shù)接受并處理望抽,當然我們也可以在middleware中間件中來做請求的接受與否的處理加矛,與middleware相比,Guards可以獲得更加詳細的關(guān)于請求的執(zhí)行上下文信息煤篙。

通常Guards守衛(wèi)層斟览,位于middleware之后,請求正式被處理函數(shù)處理之前辑奈。

下面是一個Guards的例子:

@Injectable()
export class AuthGuard implements CanActivate {
  canActivate(
    context: ExecutionContext,
  ): boolean | Promise<boolean> | Observable<boolean> {
    const request = context.switchToHttp().getRequest();
    return validateRequest(request);
  }
}
 

這里的context實現(xiàn)了一個ExecutionContext接口苛茂,該接口中具有豐富的執(zhí)行上下文信息已烤。

export interface ArgumentsHost {
 getArgs<T extends Array<any> = any[]>(): T;
 getArgByIndex<T = any>(index: number): T;
 switchToRpc(): RpcArgumentsHost;
 switchToHttp(): HttpArgumentsHost;
 switchToWs(): WsArgumentsHost;
}

export interface ExecutionContext extends ArgumentsHost {
 getClass<T = any>(): Type<T>;
 getHandler(): Function;
}

除了ArgumentsHost中的信息外,ExecutionContext還包含了getClass用戶獲取對于某一個路由處理的妓羊,控制器胯究。而getClass用于獲取返回對于指定路由后臺處理時的處理函數(shù)。

對于Guards處理函數(shù)躁绸,如果返回true裕循,那么請求會被正常的處理,如果返回false那么請求會拋出異常净刮。

(5)剥哑、interceptors攔截器
?? 攔截器可以給每一個需要執(zhí)行的函數(shù)綁定,攔截器將在該函數(shù)執(zhí)行前或者執(zhí)行后運行淹父≈暧ぃ可以轉(zhuǎn)換函數(shù)執(zhí)行后返回的結(jié)果等。

概括來說:

interceptors攔截器在函數(shù)執(zhí)行前或者執(zhí)行后可以運行暑认,如果在執(zhí)行后運行困介,可以攔截函數(shù)執(zhí)行的返回結(jié)果,修改參數(shù)等蘸际。

再來舉一個超時處理的例子:

@Injectable()
export class TimeoutInterceptor implements NestInterceptor{
  intercept(
    context:ExecutionContext,
    call$:Observable<any>
  ):Observable<any>{
    return call$.pipe(timeout(5000));
  }
}

該攔截器可以定義在控制器上逻翁,可以處理超時請求。

四捡鱼、nestjs的特點總結(jié)
??最后總結(jié)一下nestjs的優(yōu)缺八回。

nestjs的優(yōu)點:

完美的支持typescript,因此可以使用日益繁榮的ts生態(tài)工具

兼容express中間件,因為express是最早出現(xiàn)的輕量級的node server端框架驾诈,nestjs能夠利用所有express的中間件缠诅,使其生態(tài)完善

層層處理,一定程度上可以約束代碼乍迄,比如何時使用中間件管引、何時需要使用guards守衛(wèi)等。

依賴注入以及模塊化的思想闯两,提供了完整的mvc的鏈路褥伴,使得代碼結(jié)構(gòu)清晰,便于維護漾狼,這里的m是數(shù)據(jù)層可以通過modules的形式注入重慢,比如通過typeorm的entity就可以在模塊中注入modules。

完美支持rxjs

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末逊躁,一起剝皮案震驚了整個濱河市似踱,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖核芽,帶你破解...
    沈念sama閱讀 217,542評論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件囚戚,死亡現(xiàn)場離奇詭異,居然都是意外死亡轧简,警方通過查閱死者的電腦和手機驰坊,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,822評論 3 394
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來哮独,“玉大人庐橙,你說我怎么就攤上這事〗杷裕” “怎么了态鳖?”我有些...
    開封第一講書人閱讀 163,912評論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長恶导。 經(jīng)常有香客問我浆竭,道長,這世上最難降的妖魔是什么惨寿? 我笑而不...
    開封第一講書人閱讀 58,449評論 1 293
  • 正文 為了忘掉前任邦泄,我火速辦了婚禮,結(jié)果婚禮上裂垦,老公的妹妹穿的比我還像新娘顺囊。我一直安慰自己,他們只是感情好蕉拢,可當我...
    茶點故事閱讀 67,500評論 6 392
  • 文/花漫 我一把揭開白布特碳。 她就那樣靜靜地躺著,像睡著了一般晕换。 火紅的嫁衣襯著肌膚如雪午乓。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,370評論 1 302
  • 那天闸准,我揣著相機與錄音益愈,去河邊找鬼。 笑死夷家,一個胖子當著我的面吹牛蒸其,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播库快,決...
    沈念sama閱讀 40,193評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼摸袁,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了缺谴?” 一聲冷哼從身側(cè)響起但惶,我...
    開封第一講書人閱讀 39,074評論 0 276
  • 序言:老撾萬榮一對情侶失蹤耳鸯,失蹤者是張志新(化名)和其女友劉穎湿蛔,沒想到半個月后膀曾,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,505評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡阳啥,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,722評論 3 335
  • 正文 我和宋清朗相戀三年添谊,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片察迟。...
    茶點故事閱讀 39,841評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出壕吹,到底是詐尸還是另有隱情浊闪,我是刑警寧澤,帶...
    沈念sama閱讀 35,569評論 5 345
  • 正文 年R本政府宣布概荷,位于F島的核電站秕岛,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏误证。R本人自食惡果不足惜继薛,卻給世界環(huán)境...
    茶點故事閱讀 41,168評論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望愈捅。 院中可真熱鬧遏考,春花似錦、人聲如沸蓝谨。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,783評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽譬巫。三九已至稽亏,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間缕题,已是汗流浹背截歉。 一陣腳步聲響...
    開封第一講書人閱讀 32,918評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留烟零,地道東北人瘪松。 一個月前我還...
    沈念sama閱讀 47,962評論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像锨阿,于是被迫代替她去往敵國和親宵睦。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,781評論 2 354

推薦閱讀更多精彩內(nèi)容