l

2022年7月19日 星期二

《Clean Architecture實作篇:在整潔的架構上弄髒你的手》程式碼分析

July 19 09:07~10:58

▲圖1:《Get Your Hands Dirty on Clean Architecture》程式範例目錄結構

 

前言

2021年9月Teddy幫《Get Your Hands Dirty on Clean Architecture》這本書寫了書評(請參考<【還少一本書】Get Your Hands Dirty on Clean Architecture>),前陣子博碩出版社告知這本書中文版將於今年八月上市,請Teddy幫它寫推薦序。

一年多前讀過的英文版,書中有些細節已經不太記得,這幾天花了點時間把中文版用力讀過一次。這本書中文版翻譯得很好,對Clean Architecture有興趣的朋友可以參考。昨天Teddy在部落格文章<再談Clean Architecture實作>提到《Clean Architecture》書中圖22的問題,今天要談《Get Your Hands Dirty on Clean Architecture》這本書程式範例中「不那麼乾淨」的問題。

 

***

程式範例結構

Get Your Hands Dirty on Clean Architecture》的程式範例在此:https://github.com/thombergs/buckpal,鄉民們可以自行下載閱讀。

首先分析程式結構,請參考圖1。可以看出來程式碼依照書中的建議,先package by feature再package by layer。account這個package就代表一個feature(功能),因為這本書的範例程式規模很小,只示範在不同帳戶之間的轉帳功能,所以從目錄結構上看起來會覺得package by feature的味道很薄,但它真的有package by feature。

至於account裡面的domain, application, adapter相當於Clean Architecture的entity layer, use case layer, interface adapter layer。放在package最外層的BuckPalAccplication就是Clean Architecture所說的Main Component。

***

Entity Layer

接著看到Entity Layer,也就是這本書的domain layer程式碼,請參考圖2。在這一層有四個物件:Account, Activity, ActivityWindow, Mondy,書中並沒有套用Domain-Driven Design(DDD;領域驅動設計),也就是說在Entity Layer沒有Aggregate。但是從DDD的角度來看,Account似乎可以當成AggregateRoot。

但這不是重點,重點是作者在Entity Layer用了lombok這個框架用來自動產生getter/setter/constructor等。嚴格講起來在最核心的Entity Layer是不應該相依於外部工具與框架,但lombok在Java社群中是非常流行的工具,使用它可以少寫很多煩人的程式碼。lombok以annotation的形式存在程式碼中,相對而言是比較輕微的入侵。

對於框架的使用,在Clean Architecture書中有提到,使用框架之後你的系統就跟這個框架結婚。新婚的時候可能很快樂,但如果不幸日後鬧翻要離婚,那離婚手續可就很麻煩,你的財產甚至要分對方一半。以ezKanban為例,Teddy並沒有使用lombok,但為了自動序列化/反序列化將物件與JSON互轉格式,在少數Jackson無法自動判別的類別身上還是貼了Jackson annotation,如圖3所示(Jackson是一個處理JSON的工具)。

所以,如果可能在Entity Layer儘量不要使用外部框架或工具,如果真的要用,也要有廝守一輩子的心理準備。



 ▲圖2:Account程式範例

 

▲圖3:ezKanban在Entity Layer的類別上使用jackson的annotation

***

 

Use Case Layer

Use Case Layer在書中稱為application layer,請參考圖4。《Get Your Hands Dirty on Clean Architecture》書中套用六角形架構,因此在application layer底下有兩個子package:

  • port:存放application layer對外層依賴反轉的介面,又分為in port和out port。in/out的區分是從application layer的角度來看,如果這個介面是讓外層(例如web controller)用來呼叫內層,它就是一個in port(由外往內);如果它是讓application layer的物件呼叫外部服務的介面,例如用來存取資料庫的repository,這就是一個out port(由內往外)。
  • service:實作port的物件稱為service,例如實作SendMoneyUseCase介面的物件叫做SendMoneyService,你也可以把它叫做SendMonyUseCaseImpl,看你喜歡何種命名方式。



 ▲圖4:Use Case Layer (Application Layer)結構

 

接下來Teddy要開始挑毛病了,請參考圖5,SendMoneyUseCase是書中主要用來當作範例解釋的使用案例,它只有一個sendMoney方法,輸入參數是SendMoneyCommand(請參考圖6),輸出為boolean。

 

▲圖5:SendMoneyUseCase程式碼


 

▲圖6:SendMoneyCommand程式碼

 

把圖5與圖6對照到Teddy昨天<再談Clean Architecture實作>畫過的圖7:

  • Input Port:SendMoneyUseCas
  • Input Data:SendMoneyCommand
  • Output Data:boolean

 

看到這裡鄉民有沒有發現什麼問題?


▲圖7:Teddy修正《Clean Architecture》書中圖22之後的結果

 

請參考圖8,在《Clean Architecture》書中提到跨層的資料結構通常是簡單的資料結構,SendMoneyCommand是跨越application layer(use case layer)與adapter layer的物件,但是它身上的屬性卻有AccoundId, Money這兩個位於Entity Layer的Value Object。也就是說Entity Layer的物件透過SendMoneyCommand傳遞到第三層,這麼做雖然沒有違反《Clean Architecture》的依賴原則(相依性由外往內),但是卻違反了跨層原則,這個由SendMoneyUseCase所形成的Input Port(Input Boundary),不是一個完整的雙向介面。

在《Clean Architecture》書中提到理想上介面應該是雙向隔離,一開始要採用單向隔離的介面也可以,日後再隨需要調整成雙向介面。在目前的ezKanban中,Entity Layer的物件傳遞離開Use Case Layer之前一定都經過轉換,往UI層轉成DTO物件,往資料庫層轉成Data物件,領域事件傳遞到其他Bounded Context則是轉成RemoteDomainEvent物件,

 

▲圖8:《Clean Architecture》中文版第172頁

***

Interface Adapter Layer

最後看到位於Interface Adapter Layer的SendMoneyController程式碼,如圖9所示。可以很清楚看出來,SendMoneyController在第26行產生一個SendMoneyCommand物件,然後位於Entity Layer的AccountId與Money物件也被第三層(Interface Adapter Layer)的SendMoneyController給參考到。如同Teddy在上一小節所提到的,SendMoneyCommand是Input Port介面上的資料結構,它應該使用基本資料型別就好,不要使用Entity Layer的物件,以免造成系統架構不乾淨

 

▲圖9:SendMoneyController程式碼

***

結論

Get Your Hands Dirty on Clean Architecture》是一本好書,但如同所有好書一樣,讀書時必須抱持著「盡信書不如無書」的態度,如此才可深入閱讀並增進自己的思考能力。

書中範例其實還有其他問題Teddy也沒時間逐一指出,例如範例程式包含一個GetAccountBalanceService程式,請參考圖10。Teddy以為可以看到CQRS裡面的Query範例,但是這個程式有介面(GetAccountBalanceQuery)也有實作(GetAccountBalanceService),但卻沒有使用它的Controller(範例程式中沒有任何人使用到GetAccountBalanceService)。請注意它的介面回傳Money,一個位於Entity Layer的物件。它有被轉成DTO往UI傳遞嗎?誰來做這個轉換?《Clean Architecture》書中的Presenter怎麼實作?這些問題都沒有包含在程式範例裡面,鄉民們看完之後可能還是不知道怎麼做。

 

▲圖10:GetAccountBalanceService程式碼

 

最後打個廣告,如果想知道完整又乾淨的《Clean Architecture》架構與實作方法,歡迎參加【領域驅動設計與簡潔架構入門實作班】。

***

友藏內心獨白:程式碼很少的時候都看不出問題。

沒有留言:

張貼留言