架構設計準則

傳統的 MVC(Model, View, Controller) 框架,當 Controller 收到請求之後,我們會在 Controller 內直接透過 Model 去撈取資料庫的資料,並在 Controller 做資料驗證、整合、快取、商業邏輯判斷...等等的工作。

當系統越來越大,會發現很多類似的商業邏輯的程式都散在各地,沒有辦法重複再利用,當程式需要異動或修改的時候,就要去搜尋所有程式碼,把許多相同商業邏輯的程式碼去做異動,但需要修改的地方若太多,往往會東漏西漏,導致系統出現錯誤,並造成往後開發的時間成本增加。

所以我們會想要做到減少重複的程式碼提高維護開發的效率,所以將程式碼依照分類分層抽出獨立控管,讓不同類型的程式專心處理自己相關的商業邏輯,讓開發維護更容易。

隨著程式架構的演進會發展出更多不同的架構,所以這個設計架構準則也是會隨著時間做演進的。

資料來源

https://laravel5-book.kejyun.com/design-pattern/design-pattern-model-coding-structure-principle.html

根據 GitBook 台灣作者 Kejyun 的 Laravel 5 學習筆記為基礎,再次調整結構與處理原則。

資料層級需求分析

Model (模型)

在 Model 中我們僅會寫對資料表對 Eloquent 相關的設定,像是primaryKey (主鍵名稱)table (資料表名稱)以及常數,將一些撈取資料的邏輯都往Repository集中整理,放在Repository的目的是集中存取做Cache

注意:為了集中做Cache (快取)所以Model(模型)只可被Repository呼叫。

Repository (資源庫)

在不同ServiceCrontabController可能會需要對資料庫撈取同樣的資料,為了避免撈取資料的邏輯重複出現在不同的地方,我們會分出一層Repository (資源庫),將同樣撈取Model (模型)資料的邏輯都寫在一起,供不同的Servicetable存取。

Repository中,會在 function 名稱中指出這個方法是要撈什麼樣的資料,這樣方法可以重用,也可以讓程式有可讀性,不需要再去看Modelsql 條件的邏輯,判斷是在做什麼樣的處理。

e.g. PostRepository->getWeekTopPosts(); // 取得本週熱門文章

注意:呼叫單一表格資料邏輯應使用Repository (資源庫),跨表格該用Service (服務),只呼叫Model(模型),可被Service (服務)Crontab(排程)Controller(控制器)Queue(隊列)呼叫。

ExceptionCode (例外代碼)

我們在做 API 的資料存取時,會針對不同的例外狀況回傳不同的錯誤代碼 (error_code),而同一個錯誤代碼可能在不同的ControllerService被回傳,像是文章找不到我們會回傳錯誤代碼10000001,為了管理及閱讀性方便,我會分出一層ExceptionCode (例外代碼)去做共用的例外代碼管理。

像是找不到文章的代碼我會設為PostExceptionCode::POST_NOT_FOUND = 10000001;,所以在程式中只要看到PostExceptionCode::POST_NOT_FOUND就可以馬上知道這個是找不到文章的錯誤代碼。

假設是Service (服務)的錯誤可以設為PostServiceExceptionCode::UNKNOW_ERROR

ExceptionCode (例外代碼)的方法皆為靜態方法,所以可以供任何類別去做存取。

Cache (快取)

在使用Repositoy去對Model做存取得時候,如果我們資料沒有做異動,我們會將資料存放在快取中,直接讀取快取的資料,而減少對資料庫的存取,提高資料的存取效率。

而我們需要對資料快取的設定、清除快取鍵值去做管理,快取像是協助Repository做資料的存取,所以我會分出一層Cache (快取)去輔助Repository做快取的處理。

注意:Cache (快取)只能在Repository使用。

Service (服務)

原本在 Controller 處理請求時,會針對使用者的請求,做跨表邏輯的處理,而同樣的處理邏輯可能會被不同的 Controller 存取,為了讓同個處理邏輯程式能夠重複使用,所以分出一層Service (服務),將這些處理邏輯放在裡面,供不同的 Controller 存取。

Service方法中,只會針對有跨表業務邏輯的資料進行蒐集與彙整,處理過後再回傳給Controller

注意:需要跨表格處理資料才使用Service (服務)Service (服務)只呼叫Repository (資源庫)可被Crontab(排程)Controller(控制器)Queue(隊列)呼叫。

Concrete (服務組合)

可以看做是Service(服務)整合層,會在Controller去呼叫各個不同的Service(服務)去做資料的判斷處理。

有時候不同的Controller會有相同呼叫Service的邏輯順序及組合的資料,為了減少重複程式,所以分出一層Concrete (服務組合)去協助Controller去做不同Service資料的整合撈取。

在許多Controller方法中,我們使用$PostService->find($post_id);去找文章資料,並用$UserService->find($post_user_id);去找文章作者相關資訊,這些在Controller重複出現兩次以上呼叫Service邏輯的部分,我們就可以寫在Concrete中去做呼叫。

注意:Concrete (服務組合)只呼叫至少兩個Service (服務),可被Crontab(排程)Controller(控制器)Queue(隊列)呼叫。

Validator (驗證器)

為了讓資料驗證方法能夠重複使用,不需要在不同的地方去驗證相同的東西,這樣會造成驗證邏輯重複出現,若有需要異動驗證規則時,會難以維護。所以沒有選擇將這些驗證的邏輯寫在ControllerServicesRepositoryModel中。而是特別分出一層Validator (驗證器)輔助Checker(檢查器)做資料驗證。

注意:Validator (驗證)只檢查單一表格資料內容,可被Service(服務)Crontab(排程)Queue(隊列)Controller(控制器)Concrete(組合服務)呼叫。

Checker (檢查器)

每個商業邏輯需要驗證的資料不同,有些欄位在不同商業邏輯會有必填與非必填不同的差異,像是在做使用者身份驗證時,若有 Email 驗證及手機驗證,在 Email 驗證時,手機欄位為非必填欄位,在做手機驗證時, Email 為非必填欄位,但兩者皆為使用者的資料,無法強制使用者兩個欄位資料皆為必填,但在某些商業頁邏輯是必要的,但基本的資料驗證規則還是一樣,像是資料最長長度、email 格式...等等。

所以為了能夠重複使用共用的驗證器規則,所以建構了一個Checker(檢查器)的結構,去呼叫不同的Validator去檢查資料,像是同一個 Controller 處理的資料可能含有「會員」、「商品」資料,所以同時需要呼叫會員驗證器(UserValidator)商品驗證器(GoodsValidator)的驗證規則去驗證資料,所以就透過Checker(檢查器)去呼叫不同的驗證器,來達到驗證不同資料的功能,輔助 Controller 做資料驗證。

注意:Checker (檢查器)只檢查多表格資料內容,可被Service(服務)Crontab(排程)Queue(隊列)Controller(控制器)呼叫。

Support (輔助)

做共用的且不包含任何資料邏輯處理的靜態輔助方法。

像是我們可以用GoogleAnalyticSupport::api()去對 GA 的 API 做存取。

或是使用 StringSupport::generate('32')產生UUID。

在Support(輔助)的方法皆為靜態方法,所以可供任何類別去做存取。

Controller (控制器)

組合底層的所有方法進行控制,處理後回吐資料,每個 function 都使用 try catch 處理錯誤去捕獲底層傳上來的錯誤訊息。

注意:為了集中做Cache (快取),所以跟Controller(控制器)同層級的禁止直接呼叫Model(模型)

Queue(隊列)

層級上接近Controller(控制器),但是在低一點,大多用來處理非即時性資料,降低伺服器負荷。

Crontab(排程)

層級上同Controller(控制器),大多用來固定時間檢查、處理錯誤或產生資料。

Command(指令)

層級上同Controller(控制器),大多用來當有需求時,手動檢查處理錯誤或產生資料。

results matching ""

    No results matching ""