l

2021年9月8日 星期三

【還少一本書】Get Your Hands Dirty on Clean Architecture

September 08 20:30~23:34


前言

目前市面上Clean Architecture的書,跟日本進口壓縮機一樣,非常稀少。Teddy在2017年介紹過〈[還少一本書] Clean Architecture〉,事隔多年後,今天要介紹剛讀完的另外一本《Get Your Hands Dirty on Clean Architecture》。

這本書只有薄薄的137頁,書中探討實作Clean Architecture遇到的程式實作問題。Uncle Bob寫的《Clean Architecture: A Craftsman's Guide to Software Structure and Design》雖然多達400頁,但書中的敘述比較抽象,實作的程式碼趨近於0。導致許多人讀完《Clean Architecture》之後,在程式實作的時候形成「一個架構,各自表述」的窘境。而「在乾淨的架構上弄髒你的手」這本書,試圖彌補Uncle Bob留下的實作缺口。

接下來Teddy逐一介紹這本書的優點以及可改善之處。

***

優點--單一責任的使用案例類別

這本書的前兩章談傳統階層架構所造成的問題,以及如何透過依賴反轉來解決這些問題。作者在前兩章還沒提到程式實作,而這兩章的大部分內容在Uncle Bob的書中已經說得很清楚。Teddy覺得比較值得注意的一點是,書中第15頁提到:

The use cases are what we have called services earlier but are more fine-grained to have a single responsibility, thus avoiding the problem of broad services that we discussed earlier. (使用案例就是我們之前所說的服務,但更細粒度地具有單一職責,從而避免了我們之前討論的廣泛服務的問題。)

Clean Architecture的使用案例,有兩種常見的實作方式。第一種就是上述所說的broad service,一個service物件身上包含好多方法,每一個方法代表一個使用案例。DDD紅皮書《Implementing Domain-Driven Design》的書中範例就是典型的broad service寫法。

另一種方式就是將使用案例提升為類別,一個使用案例類別只會做一件事,也就是上述符合單一責任原則的fine-grained service。

Teddy自己是採用一個類別代表一個使用案例(fine-grained service)的做法。

***

優點--程式結構

在Uncle Bob書中由Simon Brown所寫的第34章The Missing Chapter,談四種程式碼結構的方式:package by layer、package by feature、ports and adapters、package by component。雖然這個章節畫了好幾張圖來解釋這四種方法,但Teddy覺得書中的描述還是少了一些重要的細節,導致在實作上有太多的可能性。

這個問題其實很容易釐清,講那麼多還不如給一張圖畫出程式目錄結構就搞定了。

……為什麼不給code哩?

而本書第3章則是以實際的程式結構範例重點說明package by layer與package by feature的優缺點,最後給出作者的建議方式:先package by feature再package by layer。

雖然作者沒有針對Uncle Bob原本書中的四種目錄結構方式詳細比較,但至少給了一個還算是非常具體的實作建議。

***

優點--具體的使用案例實作程式範例

本書第4章談使用案例的實作,以實際的程式碼對應到Uncle Bob書中所說的use case input、use case與use case output。本書把use case input稱為input model,並將其包裝成Command物件(可參考書中第37頁SendMoneyCommand程式碼)。

將input包裝成Command是很常見的作法,但這一點與Teddy的慣用做法不同,Teddy使用UseCaseInput類別來實作input。

Teddy在實作Clean Architecture的時候同時套用DDD與Event Storming,在Event Storming中藍色便利貼代表Command,也就是使用者對系統下的命令。有人將Event Storming的Command當成use case input,Teddy則是直接將Command用使用案例實作,而執行該Command所需的輸入參數當成use case input。所以Teddy把Command這個概念保留給使用案例,而不是使用案例的輸入參數。

儘管書中的實作方式與Teddy慣用方式有所差異,但概念上還是一致。

***

本章另外還有四個亮點:

  • 輸入參數驗證與商業邏輯驗證的差異,以及應該在哪個軟體架構階層中去做驗證。
  • 貧血模型(anemic model)和豐富模型(rich model)對於使用案例的實作有何影響?
  • 使用案例的輸出
  • 唯讀的使用案例實作

***

優點—跨層的物件映射(Mapping)

Teddy讀完Uncle Bob的《Clean Architecture》之後將其重點歸納為三個原則,細節請參考〈再談Clean Architecture三原則〉。其中有一個原則叫做〈跨層原則〉,書中關於此原則的實作著墨甚少,只提到當物件跨層傳遞的時候,會使用Mapper設計模式將物件映射成另一種型別。

實務上,也有不少開發人員反對跨層映射,覺得太麻煩,不但沒效益還產生許多類似的重複程式碼。

本書第8章詳細說明No Mapping、One-Way Mapping、Two-Way Mapping與Full Mapping(《Clean Architecture》書中建議的做法)這四種做法的優缺點。作者建議對於web controller layer採用full mapping,但在persistence layer就不建議使用,因為作者覺得開銷太大,不划算。

但這一點Teddy並不同意作者的看法,在ezKanban中,不管是web controller layer還是persistence layer,都採用Full Mapping。雖然一開始需要花時間寫mapper程式,但在後來Teddy不斷重構entity layer的過程中幫了極大的忙。不管entity layer實作如何修改,persistence layer與web controller layer幾乎完全不受到影響。

***

優點—Main Component的實作

Clean Architecture》第26章The Main Component也是有點抽象的一章,書中提到Main Component是「最髒」的元件,而且每一種系統配置都需要用一個Main Component來代表,在實作上有不少讀者不知如何具體落實這個Main Component。

Get Your Hands Dirty on Clean Architecture》第9章則是以具體的程式碼說明Main Component的實作,又彌補了一個《Clean Architecture》書中的一個實作缺口。

***

可以更好

提了這麼多優點,最後談談可以做得更好的地方。

第一點雖然Teddy把它列為缺點,但也可以說是優點。因為這本書很薄,雖然可以很快一窺Clean Architecture的實作面貌,但對於完全沒有學過Clean Architecture的鄉民而言,如果要光靠這本書就「徹底學會」Clean Architecture,恐怕有點難度。Teddy建議搭配Uncle Bob的《Clean Architecture》一起服用,一本學理論,另一本學實作,學習效果更佳。

第二點Teddy覺得比較嚴重,書的標題雖然是「Get Your Hands Dirty on Clean Architecture」,但作者在實作上滿多地方參考六角形架構(Hexagonal Architecture)並使用六角形架構的術語。例如,在第三章程式結構中使用inoutport這些六角形架構的術語,而非Clean Architecture書中採用的術語。雖說Clean Architecture與Hexagonal Architecture大同小異,但兩者所用的術語還是略有不同。所以在一本標榜Clean Architecture實作的書中採用「鄰國術語」,Teddy還是覺得略有不妥。

此外,硬是要用in、out來區分adapter,Teddy也是略有疑慮。作者提到in adapter代表由外部呼叫系統,out adapter代表有系統呼叫外部。例如,web controller是in adapter,repository或persistence adapter是out adapter,這沒問題。那麼Event Bus呢?它有時候是in,有時候是out,要把它放在那個目錄裡面?還是把接收資料的event bus adapter放在in,傳送資料的event bus adapter放在out?Teddy沒有這樣設計過,所以對於這種區分adapter的方法還需要思考一下。

第三點,書中第6章:Implementing a Persistence Adapter的實作建議Teddy不太認同。書中建議在這裡也套用單一責任原則,讓一個Persistence Adapter就只做一件事,例如將Account物件的載入、更新、建立設計成以下三個介面:LoadAccountPort、UpdateAccountStatePort、CreateAccountPort。Teddy比較建議在這裡套用DDD的Repository設計模式,讓一個Aggregate對應一個Repository來處理儲存的問題。

以上述的Account為例,若採用event sourcing來保存物件狀態,根本不會有書中建議的UpdateAccountStatePort與CreateAccountPort介面,只需要save與load這兩個介面。

最後一點,這是一本介紹Clean Architecture實作的書,程式範例理應很乾淨。但書中有部分範例卻「不太乾淨」,例如下圖書中第35頁的SendMoneyService,它應該位於Clean Architecture的Use Case層。直接在它身上貼上@Transactional似乎有跟框架產生耦合的嫌疑。

***

友藏內心獨白:值得一讀。

1 則留言:

  1. @Transactional應該放在什麼物件上才適合?跨Bounded context的交易如何控制與放在什麼物件上才符合clean architecture ?

    回覆刪除