摘要:通過(guò)業(yè)務(wù)處理異常,將不正常的業(yè)務(wù)處理結(jié)果返回給調(diào)用者或其他。通常會(huì)在層中寫與數(shù)據(jù)庫(kù)相關(guān)的代碼,如表的關(guān)聯(lián)關(guān)系,表屬性的可取值等。返回此類響應(yīng)表示服務(wù)器拋出了未捕捉處理的異?;蝈e(cuò)誤。
前言
之前在公司負(fù)責(zé)了一個(gè)項(xiàng)目,進(jìn)行了前后端分離,筆者負(fù)責(zé)了整個(gè)項(xiàng)目的基本結(jié)構(gòu)的搭建,在此總結(jié)一些經(jīng)驗(yàn)。本文主要介紹后端web api的設(shè)計(jì)與實(shí)現(xiàn)。demo代碼鏈接:github代碼
基本架構(gòu) 代碼分層應(yīng)用的基本架構(gòu)主要包含以下5個(gè)部分:
Controller Layer(控制器層)
Transformer Layer(轉(zhuǎn)換層)
Service Layer(服務(wù)層)
Repository Layer(倉(cāng)庫(kù)層)
Model Layer(模型層)
各個(gè)層次的主要職責(zé)如下圖所示
詳細(xì)說(shuō)明
基本的程序流程如上圖所示,從1到8。若業(yè)務(wù)邏輯比較簡(jiǎn)單,可以直接跳過(guò)Service層,由Controller層直接調(diào)用Repository層。
各層次之間可以通過(guò)依賴注入聯(lián)系起來(lái)。
業(yè)務(wù)邏輯主要分布在Service層和Model層。Service層負(fù)責(zé)工作流邏輯,即任務(wù)的具體執(zhí)行流程,如事務(wù)處理等;Model層負(fù)責(zé)領(lǐng)域邏輯,領(lǐng)域邏輯包括了業(yè)務(wù)規(guī)則、業(yè)務(wù)計(jì)算等。
通常情況下,Service層由于包含了主要的工作流邏輯,其可復(fù)用性比較差,但當(dāng)Service層的業(yè)務(wù)邏輯積累到一定程度的時(shí)候,會(huì)沉淀一些通用的業(yè)務(wù)邏輯(工作流邏輯),最好將通用的業(yè)務(wù)邏輯提取出來(lái),形成一個(gè)Service層內(nèi)的子層,稱為“通用處理層”(General Process Layer),可以將這部分代碼放到當(dāng)前Services目錄下的General目錄中。
Service層的返回值: 1.業(yè)務(wù)對(duì)象(model等業(yè)務(wù)數(shù)據(jù))2.bool值,指示處理結(jié)果。
當(dāng)Service層的業(yè)務(wù)邏輯無(wú)法正常執(zhí)行時(shí),需要拋出業(yè)務(wù)處理異常BusinessException(注意,不是程序執(zhí)行異常。業(yè)務(wù)處理異常例子:如賬戶余額不足,無(wú)法轉(zhuǎn)賬)。通過(guò)業(yè)務(wù)處理異常,將不正常的業(yè)務(wù)處理結(jié)果返回給調(diào)用者(eg:Controller或其他Service)。而在正常執(zhí)行業(yè)務(wù)邏輯的情況下,則返回Service層的正常返回值,即上面第5點(diǎn)。
在每一層中,當(dāng)新開一個(gè)子分類時(shí),最好建立一個(gè)子分類的基類。以Controller層為例子,當(dāng)需要在app/Api/Controllers/V1目錄建立一個(gè)Blog子目錄時(shí),最好在建好后的目錄中添加一個(gè)BaseController,作為該目錄下的基類。
Model層可以細(xì)分為AR(ActiveRecord)層和Domain層。Domain層通常是基于AR層。AR層中每個(gè)類對(duì)應(yīng)一張數(shù)據(jù)庫(kù)表,而Domain類中包含的數(shù)據(jù)可以來(lái)自多個(gè)AR類。
通常會(huì)在AR層中寫與數(shù)據(jù)庫(kù)相關(guān)的代碼,如表的關(guān)聯(lián)關(guān)系,表屬性的可取值等。
通常會(huì)在Domain層中寫相應(yīng)的領(lǐng)域邏輯。eg : 領(lǐng)域模型某些值的取值規(guī)則
Domain類代表一個(gè)完整的領(lǐng)域模型,而AR類則不一定構(gòu)成一個(gè)完整的領(lǐng)域模型。eg : 產(chǎn)品的數(shù)據(jù)存放在多張張表內(nèi):product_a和product_b等,因此會(huì)有多個(gè)AR類對(duì)應(yīng)這些表;同時(shí),可以引入一個(gè)名為“Product”的Domain類,它代表了一個(gè)完整的產(chǎn)品(領(lǐng)域模型)。Domain類可以基于底層AR類中一個(gè)(一般來(lái)說(shuō)是基于主表)。
目錄結(jié)構(gòu)目錄結(jié)構(gòu)如下所示:
詳細(xì)說(shuō)明
如上圖所示,各個(gè)層次Controller、Service、Transformer、Model、Repository都有自己相應(yīng)的目錄
Controllers目錄說(shuō)明(Controller層)
Controller層,所有api的控制器放在該目錄下,按版本分類(V1,V2...),版本目錄下按照業(yè)務(wù)分類
Controller層的職責(zé):
校驗(yàn)輸入
處理請(qǐng)求&構(gòu)造響應(yīng)
調(diào)用Transformer層、Service層、Repository層,但不應(yīng)該在Controller中包含任何業(yè)務(wù)邏輯
在各個(gè)版本目錄之下(V1,V2...),按照業(yè)務(wù)將Controller分到不同的子目錄中(eg:Blog,Marketing...),而不是按照數(shù)據(jù)庫(kù)進(jìn)行劃分,雖然按照業(yè)務(wù)劃分與按照數(shù)據(jù)庫(kù)劃分的結(jié)果可能一樣
每個(gè)版本目錄下有一個(gè)版本控制器(eg:V1Controller),該版本下的所有控制器需要繼承自該控制器。版本控制器必須繼承自AppHttpControllersApiController
按照業(yè)務(wù)劃分的控制器子目錄中應(yīng)該有一個(gè)控制器基類(eg:BaseController),所有該目錄下的控制器繼承自該基類控制器
Common目錄說(shuō)明
Common目錄用于放置一些在整個(gè)項(xiàng)目中都可以使用的通用代碼,通常這些代碼不應(yīng)該包含特定的業(yè)務(wù)邏輯
子目錄Components用于放置組件代碼(注意:這些組件代碼不應(yīng)該繼承自框架代碼/第三方代碼,否則應(yīng)該將其放置到Extensions目錄)。通常這些代碼能提供一個(gè)特定的功能,但又不依賴框架本身,可以作為其他項(xiàng)目的第三方包使用
子目錄Extensions用于放置擴(kuò)展了框架代碼/第三方代碼原有功能的代碼(通常意味著繼承自框架代碼/第三方代碼),注意與Components區(qū)分
子目錄Enum用于放置“常量定義”的代碼
子目錄Helpers用于放置一些工具類,工具類中通常會(huì)提供一些靜態(tài)方法,方便調(diào)用
子目錄Scopes用于放置與Eloquent ORM相關(guān)的Scopes定義
子目錄Lib用于存放一些底層的庫(kù)文件
Models目錄說(shuō)明(Model層)
Model層,所有的模型類放置在該目錄下。通常按數(shù)據(jù)庫(kù)進(jìn)行分類(eg: DbBlog)
Model層的職責(zé)(繼承自Eloquent class時(shí)):
對(duì)應(yīng)一張數(shù)據(jù)庫(kù)表,一個(gè)model實(shí)例表示表中一條記錄
處理property ,如$db, $table,$fillable等;處理scope
Accessors & Mutators : 在從model實(shí)例中獲取或存儲(chǔ)屬性時(shí)對(duì)其進(jìn)行格式化
關(guān)聯(lián)關(guān)系配置: 使用hasMany()、belongsTo()等
model本身行為的代碼(即領(lǐng)域邏輯代碼,屬于業(yè)務(wù)邏輯的一部分),包括了model在運(yùn)行時(shí)的狀態(tài)變化,如status由valid變換成invalid
Model層的職責(zé)(不繼承自Eloquent class時(shí)):
作為一個(gè)領(lǐng)域類,包含領(lǐng)域邏輯
當(dāng)一個(gè)完整的領(lǐng)域類被分割成多個(gè)數(shù)據(jù)庫(kù)表存儲(chǔ)在數(shù)據(jù)庫(kù)中時(shí),可以在各數(shù)據(jù)庫(kù)目錄(eg:DbBlog)下創(chuàng)建Domain目錄,用于存放完整的領(lǐng)域類。
所有對(duì)應(yīng)數(shù)據(jù)庫(kù)表的Model應(yīng)該間接繼承自AppModel。每個(gè)數(shù)據(jù)庫(kù)目錄下(eg: DbBlog)應(yīng)該包含一個(gè)BaseModel(代表該數(shù)據(jù)庫(kù)),其他Model繼承自該BaseModel
注意:對(duì)數(shù)據(jù)庫(kù)表進(jìn)行“增刪改查”的操作代碼請(qǐng)不要放置到Model,應(yīng)該將“增刪改查”的代碼放置到Repository層
Repositories目錄說(shuō)明(Repository層)
Repository層,所有倉(cāng)庫(kù)類放置在該目錄下。通常按照業(yè)務(wù)/數(shù)據(jù)庫(kù)進(jìn)行劃分
Repository層的職責(zé):
僅包含對(duì)數(shù)據(jù)庫(kù)直接進(jìn)行增刪改查操作的代碼,輔助Model層(除此之外請(qǐng)不要放置其他代碼;通常增刪改的邏輯比較單一,而查則會(huì)有多種情況,將各種查詢邏輯在此處實(shí)現(xiàn))
Repository層僅包含直接對(duì)數(shù)據(jù)庫(kù)進(jìn)行操作的代碼,其他涉及外部調(diào)用等功能的代碼應(yīng)該考慮放置在Service層中。
所有的倉(cāng)庫(kù)類應(yīng)該繼承自AppRepository類。
Services目錄說(shuō)明(Service層)
Service層,所有的服務(wù)類放置在該目錄下。通常按業(yè)務(wù)進(jìn)行分類
Service層的職責(zé):
處理牽涉到的外部行為:如發(fā)送郵件,使用外部API(如使用隊(duì)列,調(diào)用thrift,調(diào)用其他團(tuán)隊(duì)的服務(wù)等)
包含業(yè)務(wù)邏輯(主要是工作流邏輯(workflow logic),即完成某個(gè)任務(wù)的具體流程):service層是業(yè)務(wù)邏輯存在的主要地方,輔助Controller層;當(dāng)需要對(duì)數(shù)據(jù)庫(kù)進(jìn)行增刪改查時(shí),則應(yīng)該調(diào)用相應(yīng)的Repository層
所有的服務(wù)類都應(yīng)該繼承自AppService類
Transformers目錄說(shuō)明(Transformer層)
Transformer層,所有的轉(zhuǎn)換類放置在該目錄下。通常按照業(yè)務(wù)進(jìn)行分類。
Transformer層的職責(zé):
處理顯示邏輯
管理API接口的輸出(使接口的輸出與底層的Service,Repository,Model等解耦,這樣即使底層數(shù)據(jù)庫(kù)表進(jìn)行了修改,也可以不影響接口的使用)
所有的轉(zhuǎn)換類都應(yīng)該繼承自AppTransformer類
響應(yīng)注意 : 這里討論的響應(yīng)格式指的是應(yīng)用業(yè)務(wù)相關(guān)的響應(yīng),由第三方提供的api接口的響應(yīng)不納入處理范圍(eg:laravel passport提供的響應(yīng),swagger提供的響應(yīng))
響應(yīng)分類成功類響應(yīng):http響應(yīng)碼介于200~300。返回此類響應(yīng)表示服務(wù)器完整處理了該請(qǐng)求,沒(méi)有未捕捉處理的異常或錯(cuò)誤。(除了正常情況,在業(yè)務(wù)邏輯處理失敗時(shí),也會(huì)返回此類響應(yīng),同時(shí)會(huì)帶上相應(yīng)的業(yè)務(wù)處理失敗信息)
失敗類響應(yīng) : http響應(yīng)碼不介于200~300。返回此類響應(yīng)表示服務(wù)器拋出了未捕捉處理的異?;蝈e(cuò)誤。
響應(yīng)例子 成功類響應(yīng)1.業(yè)務(wù)邏輯處理成功
2.業(yè)務(wù)邏輯處理失敗
結(jié)構(gòu)如上圖所示:結(jié)構(gòu)與業(yè)務(wù)邏輯處理成功是一樣。區(qū)別在于成功時(shí)的code為0,失敗時(shí)則為相應(yīng)的錯(cuò)誤碼,code的取值為為appCommonEnumErrorCode.php中的業(yè)務(wù)級(jí)錯(cuò)誤碼(見(jiàn)下面的錯(cuò)誤碼)。
失敗類響應(yīng)
失敗響應(yīng)的格式配置在文件config/api.php中(關(guān)鍵詞為:errorFormat)。主要包括了message、errors、code、status_code、debug。有些信息在生產(chǎn)環(huán)境不會(huì)展示。
響應(yīng)格式化處理的大致思路:對(duì)特定的請(qǐng)求(對(duì)此類請(qǐng)求做標(biāo)記)的處理結(jié)果,在返回給用戶時(shí)進(jìn)行攔截(使用事件機(jī)制),對(duì)原有響應(yīng)進(jìn)行格式化處理。
響應(yīng)的代碼:
AppHttpMiddlewareBusinessFormatOutput : 路由中間件,在某些路由放置該中間件,則標(biāo)記該請(qǐng)求,表明其響應(yīng)需要進(jìn)行格式化處理
AppListenersAddBusinessStatusToResponse : 事件handler,處理由dingo觸發(fā)的ResponseWasMorphed事件,對(duì)響應(yīng)進(jìn)行格式化處理
AppHttpControllersApiController.php文件中的常量BusinessStatusHeader,通過(guò)響應(yīng)中的header為中介,將業(yè)務(wù)邏輯處理結(jié)果傳遞到2中的事件handler中,并最終構(gòu)成格式化響應(yīng)。
錯(cuò)誤碼錯(cuò)誤碼相關(guān)的代碼文件為:appCommonEnumErrorCode.php
錯(cuò)誤碼格式:A-BB-CCC
A : 表示錯(cuò)誤級(jí)別,0代表成功,1代表系統(tǒng)級(jí)錯(cuò)誤,2代表服務(wù)(業(yè)務(wù))級(jí)錯(cuò)誤;
B : 表示項(xiàng)目/模塊/分類;
C : 具體錯(cuò)誤編號(hào);
不同錯(cuò)誤級(jí)別錯(cuò)誤碼的使用:
業(yè)務(wù)級(jí)錯(cuò)誤碼用于表示業(yè)務(wù)處理結(jié)果。
Service層業(yè)務(wù)處理失敗,拋出BusinessException時(shí)使用業(yè)務(wù)級(jí)狀態(tài)碼
Controller層構(gòu)造響應(yīng)時(shí),定義響應(yīng)的業(yè)務(wù)處理結(jié)果,eg:return $this->response->array($validator->errors()->toArray())->withHeader(self::BusinessStatusHeader, [ErrorCode::BUSINESS_INVALID_PARAM, "業(yè)務(wù)處理結(jié)果信息"]);
用于日志記錄(業(yè)務(wù)相關(guān)的日志)
系統(tǒng)級(jí)錯(cuò)誤碼用于表示代碼運(yùn)行異常。
用于記錄系統(tǒng)性異常日志,Controller、Service、Transformer、Repository、Model各個(gè)層皆可
注意:
錯(cuò)誤碼文件不能重寫,若有新的錯(cuò)誤碼,請(qǐng)按現(xiàn)有分類添加,不能刪除或修改舊的錯(cuò)誤碼。
異常與異常處理異常相關(guān)的代碼:app/Exceptions目錄。在應(yīng)用代碼中,只能拋出BusinessException或者是SystemException。請(qǐng)不要拋出其他的異常,不同異常通過(guò)異常的code來(lái)區(qū)分(code的定義在app/Common/Enum/ErrorCode.php)。
當(dāng)業(yè)務(wù)邏輯執(zhí)行失敗時(shí),拋出BusinessException,常見(jiàn)可能情況如下:
Controller層校驗(yàn)輸入失敗,拋出BusinessException
Service層業(yè)務(wù)邏輯執(zhí)行失敗,直接拋出BusinessException(如賬戶余額不足,無(wú)法轉(zhuǎn)賬)
Service層業(yè)務(wù)邏輯執(zhí)行失?。ǖ珱](méi)有拋出異常,而是通過(guò)返回值指明執(zhí)行失?。?,則接受到該返回值的調(diào)用者拋出BusinessException
Controller必須捕捉BusinessException(因此即使拋出了BusinessException,依然要返回一個(gè)成功類響應(yīng)(見(jiàn)上文)),并根據(jù)BusinessException的相應(yīng)信息構(gòu)造響應(yīng)。建議所有Controller的action以下面的格式進(jìn)行編寫。
public function add(Request $request, ReserveService $reserveService){ try{//將所有的控制器邏輯放到try塊中 $postData = $request->post(); //校驗(yàn)數(shù)據(jù)有效性 /** @var IlluminateValidationValidator $validator*/ $validator = Validator::make($postData, [ "orderName" => "required", "reservePhone" => "required", ]); if($validator->fails()){//校驗(yàn)失敗 new BusinessException(ErrorCode::BUSINESS_INVALID_PARAM, "", $validator->errors()->toArray()); } $result = $reserveService->addReservation($postData); if(true === $result){ //業(yè)務(wù)邏輯執(zhí)行成功 return $this->response->array([]); }else{ //通過(guò)返回值指示業(yè)務(wù)邏輯執(zhí)行失敗 throw new BusinessException(ErrorCode::BUSINESS_BUSY); } } catch (BusinessException $e){//捕捉BusinessException,根據(jù)異常的信息構(gòu)造響應(yīng),下面這段代碼可以通用 return $this->response->array($e->getExtra()) ->withHeader(self::BUSINESS_STATUS_HEADER, [$e->getCode(), $e->getMessage()]); } }
當(dāng)發(fā)生底層系統(tǒng)異常時(shí),拋出SystemException。沒(méi)有捕捉處理的SystemException會(huì)造成一個(gè)失敗類響應(yīng)(見(jiàn)上文)。
日志與預(yù)警日志組件與預(yù)警組件的存在是為了更好的維護(hù)項(xiàng)目,及時(shí)處理bug。應(yīng)該根據(jù)自己的需要添加相應(yīng)的日志組件和預(yù)警組件。
文檔可以選擇集成一個(gè)成熟的文檔工具,如swagger,blueprint等。
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://www.ezyhdfw.cn/yun/31600.html
摘要:作為微服務(wù)的基礎(chǔ)設(shè)施之一,背靠強(qiáng)大的生態(tài)社區(qū),支撐技術(shù)體系。微服務(wù)實(shí)踐為系列講座,專題直播節(jié),時(shí)長(zhǎng)高達(dá)小時(shí),包括目前最流行技術(shù),深入源碼分析,授人以漁的方式,幫助初學(xué)者深入淺出地掌握,為高階從業(yè)人員拋磚引玉。 簡(jiǎn)介 目前業(yè)界最流行的微服務(wù)架構(gòu)正在或者已被各種規(guī)模的互聯(lián)網(wǎng)公司廣泛接受和認(rèn)可,業(yè)已成為互聯(lián)網(wǎng)開發(fā)人員必備技術(shù)。無(wú)論是互聯(lián)網(wǎng)、云計(jì)算還是大數(shù)據(jù),Java平臺(tái)已成為全棧的生態(tài)體系,...
摘要:年,和前端開發(fā)者與應(yīng)用程序前端開發(fā)者之間產(chǎn)生了巨大的分歧。開發(fā)最常見(jiàn)的解決方案有手機(jī)和平板的原生應(yīng)用程序桌面應(yīng)用程序桌面應(yīng)用程序原生技術(shù)最后,前端開發(fā)者可以從瀏覽器開發(fā)中學(xué)習(xí)到,編寫代碼不需要考慮瀏覽器引擎的限制。 前端開發(fā)者手冊(cè)2019 Cody Lindley 編著 原文地址 本手冊(cè)由Frontend Masters贊助,通過(guò)深入現(xiàn)代化的前端工程課程來(lái)提高你的技能。 下載:PDF ...
摘要:年,和前端開發(fā)者與應(yīng)用程序前端開發(fā)者之間產(chǎn)生了巨大的分歧。開發(fā)最常見(jiàn)的解決方案有手機(jī)和平板的原生應(yīng)用程序桌面應(yīng)用程序桌面應(yīng)用程序原生技術(shù)最后,前端開發(fā)者可以從瀏覽器開發(fā)中學(xué)習(xí)到,編寫代碼不需要考慮瀏覽器引擎的限制。 前端開發(fā)者手冊(cè)2019 Cody Lindley 編著 原文地址 本手冊(cè)由Frontend Masters贊助,通過(guò)深入現(xiàn)代化的前端工程課程來(lái)提高你的技能。 下載:PDF ...
摘要:年,和前端開發(fā)者與應(yīng)用程序前端開發(fā)者之間產(chǎn)生了巨大的分歧。開發(fā)最常見(jiàn)的解決方案有手機(jī)和平板的原生應(yīng)用程序桌面應(yīng)用程序桌面應(yīng)用程序原生技術(shù)最后,前端開發(fā)者可以從瀏覽器開發(fā)中學(xué)習(xí)到,編寫代碼不需要考慮瀏覽器引擎的限制。 前端開發(fā)者手冊(cè)2019 Cody Lindley 編著 原文地址 本手冊(cè)由Frontend Masters贊助,通過(guò)深入現(xiàn)代化的前端工程課程來(lái)提高你的技能。 下載:PDF ...
摘要:第二部分學(xué)習(xí)前端開發(fā)第二部分指出了學(xué)習(xí)成為一個(gè)前端開發(fā)者所需的自學(xué)資源和教學(xué)資源譯者注教學(xué)資源包括有講師指導(dǎo)的付費(fèi)課程計(jì)劃學(xué)院和訓(xùn)練營(yíng)。第三部分前端開發(fā)工具第三部分簡(jiǎn)要地介紹和指出了一些前端圈內(nèi)的工具。 參與者(排名不分先后):blueken; brucecham; cfanlife; DDU1222; LittlePineapple; MatildaJin; MAYDAY1993;...
閱讀 1372·2021-11-16 11:45
閱讀 2314·2021-11-02 14:40
閱讀 3965·2021-09-24 10:25
閱讀 3079·2019-08-30 12:45
閱讀 1360·2019-08-29 18:39
閱讀 2531·2019-08-29 12:32
閱讀 1734·2019-08-26 10:45
閱讀 1980·2019-08-23 17:01