September 06 20:50~22:22
▲ezKanban的tag功能
前幾天有一位朋友問Teddy:「Clean Architecture提到不要把Entity Layer的物件直接傳到UI端,但真的有需要跨層的時候把物件都轉成DTO再往外傳嗎?」
***
以ezKanban為例子
ezKanban是一個支援多人同時使用的看板系統,圖1為其簡化版的領域模型。Board代表一個看板,其中包含了若干個Workflow(工作流),每個Workflow可以有若干的Lane。Lane區分成兩種,垂直的Lane稱為Stage(階段),水平的Lane稱為Swimlane(泳道)。Card(卡片)可以被放到Lane上面,並在不同的Lane之間移動。
▲圖1:ezKanban簡化版領域模型
假設Board被直接傳到前端,前端設計出如圖2所示的畫面。
▲圖2:ezKanban畫面
到目前為止沒什麼問題,前端UI物件模型與後端的領域模型大致上是一致的。一直到有一天客戶有新的需求:
- 希望增加Tag的功能來將Card分類
- 每個Board都可以有自己的Tag,不會與其他Board共用Tag
- 一張Card可以有多個Tag
如果從UI的角度來思考:「Board包含Tag,然後這些Tag才可以被指定到屬於這個Board的卡片裡面。」為了能夠在畫面上顯示Tag,前端的人很可能會要求後端將Tag加入Board物件成為它的屬性,變成如圖3所示的關係:
▲圖3:因為UI的需求,導致Board與Tag產生關係
***
問題出在哪裡?
直接把Entity Layer的物件傳給前端違反單一責任原則。Entity Layer的物件之所以會存在,是為了解決或是表達問題領域的重要概念與商業邏輯。如果把它們直接傳給UI,拿來當作顯示畫面之用,Entity Layer的物件就擔負了兩種責任:
- 表達商業邏輯與概念
- 表達顯示邏輯
如此一來Entity Layer的物件就可能因為兩種不同客戶端的需求改變而跟著改變,換句話說Entity Layer與UI產生耦合,導致程式難以理解、修改與維護。
***
如何解決?
回歸到Clean Architecture與領域驅動設計(Domain-Driven Design;DDD)的角度,先確定商業邏輯,至於使用者介面是屬於細節,等Entity Layer與Use Case Layer確定之後,再交給Presenter產生View Model來滿足前端顯示的需求即可。
回到幫Card貼Tag的需求,把Tag歸類為Board的屬性並不合理,因為Tag並不是要貼在Board上面,使用者只需要知道這個Board裡面建了多少種類的Tag,再拿這些Tag貼在卡片上面。也就是說,Tag只需要知道它自己屬於哪一個Board的單向關係即可,Board根本不需要知道Tag,如圖4所示。
▲圖4:Board, Card與Tag的關係
至於前端所需的所有資料,如圖5所示另外設計一個BoardContentViewModel。這個View Model是由Presenter為了前端所需而動態產生,Entity Layer並沒有一個這樣的靜態Model。
▲圖5:ezKanban管理Tag畫面
最後的領域模型,Tag物件不屬於Board,使用者可以獨立新增與修改Tag,也很容易直接將Tag指定給Card,請參考圖6與圖7。
▲圖6:ezKanban管理Tag畫面
▲圖7:ezKanban顯示Card上面多個Tag
***
友藏內心獨白:UI歸UI,Domain Model要分明。
首先,我很配服你的圖文以及排版能力,深入淺出的教學。
回覆刪除但因為你涉及的領域不是我所熟悉的PHP、Java、HTML以及以外的Ruby、python等等。
你的問題很簡單。
大部份的程式設計師不知道。
他們設計完後的成品。
要給客戶端能夠(自行設計),像魔獸爭霸的「地圖建置系統」,世紀帝國的「自訂遊戲」。魔法門之英雄無敵的「自創地圖」。
劇情、地圖、場景,全部都可以讓玩家自行發揮。
所以當你把功能通通都寫死在裡面的時候,你的系統就只剩你能改而已。
這個就要分二個取向,不應該這麼簡單述明。
因為這文章的「預設基礎」是這個軟體的使用者(公司),他們有請人定期維護這個軟體,讓他們依據業務需求對「內容進行變動」。
你才需要把這功能向外建置。
如果不是。
全部嵌在裡面,可以減少程式運行效能,節約電能。
我們不應該為了「沒人使用的流動性」,而特意把這東西建置在「前端」。
而且考量到利益面。
假設這公司10年內固定的使用軟體版本,只有每年有變動的需求。
那這期間的維護,會在額外委聘設計者調整,也是一筆收入。
所以過度的設計「自定功能」給客戶使用,還昌追求極簡化「效率與利益」,絕對沒有單一的判斷基準。