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