相信大家或多或少都有去公家機關辦事的經驗。去公家機關辦事時,如果等待時間拖太久,肯定覺得很煩吧?好不容易等到了,這時萬一辦事員再來一句:「資料不足,回家補足再來!」哇,一下午就這樣去了!「資料不足這種小事,早點跟我說不行嗎?」離開時口中不自覺嘮叨的經驗,肯定也不少有吧?
號碼牌示意圖,圖片截自蝦皮購物
今天我們要來實際預演一下,看看在收到一個需求後,到底要怎麼安排 Clean Architecture 的四層架構。
程式,就是讓電腦去解決你真實世界發生的問題。而根據引用的不同理論,或是使用了不同的設計方式,解決的方式也不同。譬如「物件導向」程式,則是試圖將真實世界參與一件事情的人事物,都用「物件與物件間的互動」來模擬。我知道很抽像。沒關係,最好理解抽象描述的方法就是舉例!
我們回到許久不見的教務處。在幾十年前沒有電腦的年代,人們是怎麼「申請獎學金」的?假設你準備要申請獎學金了,你在家填好申請單,走進教務處,接下來發生的事會有哪些?我們來試著在腦中演練一下…
首先,門口會有一位工讀生上前招呼,並且詢問你的來意。在了解你的來意後,會先檢查你的申請表是否有確實填寫,以免你因為一些資料不完整而被退件,浪費教務處專員的時間,以及,當然,你的等待時間。
接著,工讀生會引導你去一個寫著「獎學金申請」的櫃台前椅子上坐下來排隊等候。等輪到你時,負責的專員會叫你的名字,請你上前辦理。專員會接過你的申請單,接著做一連串動作:
在以前沒有電腦的年代,(我猜想)學校的教務處就是這麼處理「申請獎學金」的工作的。
回到現代,我們的物件導向程式該怎麼模擬上述的流程?該創建哪些物件?這些物件又該被放在 Clean Architecture 的哪一層?以下是筆者自己的分析結果,各位可以參考一下:
登場人物 | 任務 | 物件名 | 分層 |
---|---|---|---|
工讀生 | 詢問來者意圖,初步檢查文件完整性,為其帶路,找到合適的專員 | ApplyScholarshipController | Adapter |
申請書 | 寫著申請資料,專員審核時可以參考 | ApplicationForm | UI/Adapter |
「獎學金申請」專員 | 控管流程,根據申請書的資料,向檔案管理員索取資料,依規定審核後填寫正式記錄 | ApplyScholarshipService | Use Case |
檔案管理員 | 依專員需求,找出資料或儲存資料 | ScholarshipRepository | Use Case / Adapter |
「獎學金申請」正式記錄 | 記載此申請的官方資料與核心運算(如有需要) | ScholarshipApplication | Entity |
檔案櫃 | 存放檔案 | - | DB |
上述是筆者針對真實世界的案例,分析出的一些重要物件、其主要任務,與其在 Clean Architecture 階層架構中的所在位置。
下一篇起,我們會從 Interface Adapter 開始,一層一層地往下實作與測試,大致上會依照上表的規劃,但如有需要修正或重構,將會當場修正。
謎之聲:「人生如戲,戲如人生。」
ithelp2021
你好 想請問一下
基於上述的案例,如果想要追加 申請次數限制的功能
有兩個想法
寫一個新用例 ApplyScholarshipServiceWithCounter,內容將會跟 ApplyScholarshipService 大致相同,但多了對 Counter(Entity)的檢查,這樣的部分重複是能接受的嗎?
寫一個新用例 CheckCounter,就是想法1檢查的部分,將他分離出來,由 ApplyScholarshipController 呼叫,未通過則不執行 ApplyScholarshipService
想請問作者會如何追加這個功能?
謝謝
首先感謝您的提問,這的確是日常生活中蠻常遇到的問題。
如果沒誤會,您的意思應該是指「申請者不可以一個獎學金申情很多次」吧?
如果是的話,我認為這在現實生活中,應該可以由「承辦專員」發起,亦即,他請另一個管申請單的管理員找看看這位同學申請的記錄,然後後面的事應該就可以繼續了。
您的 Solution 1 我覺得蠻可行的,但您所謂的「重複」是什麼意思我就不解了,如果學校的獎學金政策有所修改,那麼承辦專員應該要知道,因此直接改 Service 應該就能解決問題。反正有測試,記得測試一併修改就好 :)
至於 Solution 2,我會覺得,讓門口工讀生去找記錄管理員要同學申請記錄,也不是不行,但一般較有記律的學校應該要避免這種事發生,所以我覺得如非必要,否則不要這樣寫程式,不然會與現實不符。
希望有回答到您的問題 :)
感謝您的回答
申請次數限制是指一天能申請的上限次數(可能申請沒過被打回票)
不過理解成「申請者不可以一個獎學金申情很多次」也沒有問題
Solution 1 的重複是指 ApplyScholarshipService 也保留了而不是修改,因為假設了 Service/ServiceWithCounter 會作切換(流量緊張時),所以才有重複之說(兩者很像僅差一個判斷),但這兩種 Usecase是不是應該看作獨立的功能會比較適當?
Solution 2 讓表示層作這個判斷確實不妥
整理了一下思緒 (考慮服務是能切換的)
以現實的角度來看
比較偏好 2* 這樣日後有新增其他(前置檢查)機制時 Service不用做更動,寫一個新的 IValidator注入即可。
但 2的作法可能比較務實(?),要啥寫啥,畢竟引入IValidator是我對系統未來變更的賭注
我會覺得這些原則都是通則而不是規定,我寫這系列文章目的也是為了推廣 Unit Test 的習慣與乾淨的架構。
什麼層做什麼事,不做什麼事,其實我也覺得大家習慣就好,說不定兩年後四層就落伍了,六層當道也不一定 XD
我覺得您的考量我沒有評論的立場,畢這只是一個例子,我也不是什麼大神。只要大家都有寫測試,幾個月後覺得不適合了再來改,也是很簡單的事,對吧 :)
但我其實蠻喜你最後那個 IValidator 的 idea 的 :)