友偶閱網(wǎng)文一篇衣吠,頭中尾英,閱不能颈抚,遂中譯之。原文鏈接 :Keeping your Laravel applications DRY with single action classes
“這段代碼該放在哪里责静?”恐怕是在談?wù)搼?yīng)用結(jié)構(gòu)時最經(jīng)常提起的問題「乔牛“我應(yīng)該放在 Controller 里嗎灾螃?還是 Model ?還是哪里揩徊?”睦焕,雖然 Laravel 是個十分靈活的框架,但要解答這個問題也不總能簡單明了靴拱。
當(dāng)你知道你的應(yīng)用程序只有一個接入點的時候,把邏輯寫在 Controller 是完全沒問題的猾普。但如今應(yīng)用一般都會有好幾個接入點共用同一個功能袜炕。
例如,很多應(yīng)用都會有一個用戶注冊的表單初家,提交到 Controller 偎窘,然后根據(jù)是否成功注冊來返回有用的信息。在移動端應(yīng)用的場景下溜在,一般都會有個使用 JSON 格式返回的 API 專門用于用戶注冊陌知。而且利用 Laravel 的 artisan 命令來創(chuàng)建用戶也很常見,尤其是在項目前期的開發(fā)階段掖肋。
這些重復(fù)代碼的確看上去沒什么毛病仆葡,但如果就這樣讓業(yè)務(wù)邏輯繼續(xù)擴充下去,例如:你希望給新注冊用戶發(fā)一封提醒郵件志笼,那么你需要在兩個 Controller 里同時加上這個邏輯沿盅。所以為了代碼能夠整潔優(yōu)雅起來,我們需要把它放到其他地方去纫溃。
別把服務(wù)類神化了
一開始腰涧,可以先臨時創(chuàng)建一個類,把所有針對某個業(yè)務(wù)邏輯的代碼整理起來紊浩,例如:
這樣看上去就好多了窖铡;我們可以直接從 Controller 里調(diào)用 User 的 create()
/ delete()
,通過任意途徑返回結(jié)果坊谁。然而這方式有個問題:我們處理業(yè)務(wù)邏輯的時候很少使用一種模式费彼。
例如我們有這個業(yè)務(wù)邏輯:當(dāng)用戶創(chuàng)建賬號的時候,需要創(chuàng)建一篇新的博客呜袁。如果我們繼續(xù)使用剛才的模式敌买,我們需要創(chuàng)建 BlogService
然后使其作為依賴注入到 UserService
中:
很顯然,當(dāng)我們的應(yīng)用變得越來越大阶界,會有越來越多的服務(wù)類虹钮,有些還依賴了 5 個甚至 6 個其他的服務(wù)類聋庵,代碼像被貓玩過的毛線球剪不斷理還亂 —— 我們無論如何都要避免這種結(jié)局。
引入動作類
如果我們不使用里面塞了幾個方法的單一服務(wù)類芙粱,而是把邏輯切分到好幾個類里面呢祭玉?我在最近的好幾個工程里都使用了這種模式,結(jié)果十分不錯春畔。
首先脱货,我們暫時先把“服務(wù)”這個抽象而且模糊的術(shù)語丟掉,然后引入“動作”類律姨,并且賦予以下定義:
- 一個動作類振峻,它的名字應(yīng)該要能夠讓人一眼看出它是干什么的,例如:CreateOrder, ConfirmCheckout, DeleteProduct, AddProductToCart,…..
- 動作類應(yīng)該只擁有一個公開接口(API)择份。理想情況下應(yīng)該用統(tǒng)一的名字扣孟,例如 handle(), execute() 這樣,方便我們需要在動作上實現(xiàn)一些模式荣赶,例如適配器凤价。
- 動作類不應(yīng)該關(guān)心 Request 和 Response。動作類不處理任何 Request拔创,也不產(chǎn)生任何 Response利诺,因為這是 Controller 的責(zé)任。
- 動作類可以依賴其他的動作剩燥。
- 當(dāng)處理業(yè)務(wù)邏輯的時候慢逾,遇到不能返回期望內(nèi)容的情況下,必須拋出異常灭红,讓調(diào)用者(或者 Laravel 的 ExceptionHandler)去負(fù)責(zé)渲染或者返回錯誤信息氛改。
創(chuàng)建 CreateUser 動作
現(xiàn)在,讓我們用 CreateUser 動作類來重構(gòu)剛才的例子比伏。
你可能覺得奇怪胜卤,為什么代碼要在 Email 存在的情況下拋出異常,這個不應(yīng)該是在請求的時候就應(yīng)該驗證一次嗎赁项?當(dāng)然應(yīng)該讓驗證器去做這件事葛躏,不過,強制動作類遵循業(yè)務(wù)邏輯是一個好主意悠菜。這樣做不但能夠讓業(yè)務(wù)邏輯更明確(不用過多考慮異常情況)舰攒,也更容易調(diào)試。
下面是我們使用動作類重構(gòu)后的控制器:
現(xiàn)在我們再也不用在注冊流程更改的時候同時修改兩邊的代碼悔醋,干凈整潔摩窃。
內(nèi)嵌動作
當(dāng)我們需要往應(yīng)用里倒入 1000 個用戶的時候,我們可以寫一個依賴 CreateUser 的動作類:
干凈整潔,簡單易懂猾愿。在這里我們在 Collection::map()
里重用了 CreateUser 鹦聪,返回一個集合,裝著新鮮出爐的用戶們蒂秘。我們可以稍作優(yōu)化:當(dāng)有重復(fù)郵件的時候泽本,我們可以通過返回 Null 對象,或者往 Logger 里丟 INFO 姻僧,隨你喜歡规丽。
裝飾一下 Actions
現(xiàn)在,假設(shè)我們要往日志里記錄每一個用戶的注冊撇贺。我們可以直接把代碼放到動作自身上赌莺,但我們也可以使用裝飾器模式。
然后松嘶,使用 Laravel 的 IoC 容器雄嚣,我們可以把 LogCreateUser 類綁在 Createuser 類上,這樣當(dāng)我們就總能在需要后者的時候把原型注入進(jìn)去:
這樣做甚至更容易地通過環(huán)境變量或者配置控制日志的開啟和關(guān)閉:
總結(jié)
使用這種模式大概會在開始先產(chǎn)生一大堆的類喘蟆。當(dāng)然,為了減少一下文章篇幅讀起來比較方便鼓鲁,這里只舉了這個簡單的用戶注冊例子蕴轨。當(dāng)復(fù)雜度增加的時候,這個模式的價值就開始體現(xiàn)出來了 — — 因為你清楚這些邊界清楚分工明確的代碼在哪骇吭。
使用動作類的好處:
- 使用小物件管理領(lǐng)域邏輯可避免重復(fù)代碼以及增加可重用性橙弱,Keep things SOLID。
- 易于針對不同的場景做獨立測試燥狰。
- 清晰易懂棘脐、有意義的命名讓理解項目變得更加容易
- 跨項目一致性:避免代碼七零八落地分布在 Controller、Models 等等……
而且龙致,這些實踐都是基于我最近這些年使用 Laravel 的經(jīng)驗以及我寫過的項目總結(jié)出來的蛀缝。它們對我來說非常管用,以至于我甚至在中小型項目里使用目代。
我非常希望能夠與你一同交流分享屈梁,如果你有不同的實踐方式那就最好不過了!