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