2024-09-18 18:52:00
September 18 17:52~18:52
▲這一集換Eiffel上場
工商服務
想了解本系列文章完整內容,請參考【重構既有系統:邁向整潔架構實作班】。課程介紹與報名網址在此:https://teddysoft.tw/courses/refactor-to-ca/。
***
前言
上一集<重構既有系統,邁向整潔架構 (5):第二回合,套用DDD戰術模式>在基本領域模型中套用領域驅動設計(DDD)的戰術模式(Tactical Design Pattern),找出聚合(Aggregate),達到封裝與決定交易邊界的目的。這一集要處理程式碼最多的TaskList。
分而治之
TaskList程式碼如圖1、圖2所示,這時候可以將execute mehtod所呼叫的哪幾個private method升等成Class,以簡化TaskList的長度,並且可以觀察原本這些private method與TaskList之間的耦合關係。
▲圖1:TaskList程式碼,1/2
▲圖2:TaskList程式碼,2/2
***
Use Cases Layer新住戶
Teddy將這些被升等成類別的private method放到usecase package,如圖3所示。現在Clean Architecture已經慢慢成形,最重要的兩層(entity與usecase)已經都有「住戶」了。
▲圖3:將TaskList的private method升等成類別
***
區分Command與Query
為了讓use cases layer的成員責任更佳明確,Teddy進一步套用CQRS設計模式。同時,利用use case來「吶喊」系統功能;請參考圖4。
▲圖4:在 use cases layer吶喊系統功能,並將讀寫分離。
***
接下來看一個Command的程式範例,圖5為AddTaskUseCase介面,這是一個in port,讓第三層的Rest Controller呼叫。
▲圖5:AddTaskUseCase介面
Teddy把實作Use Case介面的物件稱為Service,圖6為AddTaskService程式碼。
▲圖6:實作AddTaskUseCase的AddTaskService
Query程式與Command類似,差別在於兩者實作的介面不同,請參考圖7與圖8。
▲圖7:ShowUseCase介面
▲圖8:實作ShowUseCase的ShowService
***
剩下的execute
重構至此還沒討論圖1中的execute method,它的用途是用來呼叫use cases layer的那些Command與Query。Teddy一樣幫它座艙升等為類別,請參考圖9。但是這個升等之後的Execute class不像use case,比較像分派工作給不同use case的dispatcher。在架構上現在還妾身不明,Teddy先把它直接丟在use cases layer最外層。
請考圖9,Use case layer重構至此已經差不多了。其中有一些細節,像是Repository、Mapper、DTO、Presenter這些設計模式,全部寫出來內容太多。請容許Teddy偷懶一下,在此省略。有興趣又有錢的鄉民,歡迎報名參加【重構既有系統:邁向整潔架構實作班】,課程中會有詳細說明。
▲圖9:重構後的完整usecase package目錄結構
***
下集預告
重構至此Clean Architecture的四層已經完成了兩層,下一集要處理interface adapters layer。
***
友藏內心獨白:人口越來越多,物件村快變成物件「市」了。
2024-09-17 17:58:00
September 17 16:20~17:57
▲被封裝在聚合內部的咪咪
工商服務
想了解本系列文章完整內容,請參考【重構既有系統:邁向整潔架構實作班】。課程介紹與報名網址在此:https://teddysoft.tw/courses/refactor-to-ca/。
***
前言
上一集<重構既有系統,邁向整潔架構 (4):第一回合,分層與移除基本型別依戀>已經形成了基本的領域模型,這一集要在領域模型中進一步套用領域驅動設計(DDD)的戰術模式(Tactical Design Pattern),找出聚合(Aggregate),達到封裝與決定交易邊界的目的。
領域模型: 決定Entity, Value Object與Aggregate
圖1是上一集重構後的領域模型,ToDoList, Project, Task是Entity,Project Name是Value Object。為了簡化起見,Teddy將整個領域模型包成一個ToDoList Aggregate。
在DDD中,Aggregate與Repository是一對一的關係,一個Aggregate透過一個Repository存入資料庫。決定了Aggregate的邊界,之後如果有儲存領域模型狀態的需求(絕大多數的軟體系統都會有持久化的需求),只需要實作ToDoListRepository即可。
▲圖1:領域模型的四個類別
***
將Entity Id升級成Value Object
DDD的Entity是一個有「唯一識別符號(unique ID)」的物件,原本的ToDoList缺少這個id,因此新增ToDoListId value object作為它的id。Project可以用ProjectName當作它的id,至於Task有一個long id屬性可以當作它的id,但是考慮到以後Task id有可能是使用者自己指定的字串,因此一併幫它新增TaskId value object作為它的id。
增加兩個Value Object之後的領域模型如圖2。
▲圖2:領域模型現在有六個類別
***
封裝聚合
DDD的Aggregate是一個交易邊界,同時也是一個封裝單位。操作Aggregate內部物件的動作必須透過AggregateRoot,以避免客戶端破壞Aggregate Invariant。如果Aggregate回傳它內部Entity給客戶端,客戶端不可以直接修改這個Entity,以避免客戶端繞過Aggregate Root修改了Aggregate。
換句話說,Aggregate如果回傳內部Entity參考給外部物件,則這個Entity應該是唯讀物件。
舉個例子,圖3是ToDoList(Aggregate Root)的getProjects()方法,回傳其內部的List<Project>。客戶端拿到這個List<Project>物件之後,有兩個途徑可能繞過ToDoList而破壞封裝:
第一點可以透過回傳一個「不可修改的List」來避免,至於第2點就只能靠ToDoList將Project轉成自己設計的ReadOnlyProject來避免。
▲圖3:ToDoList::getProjects() 程式碼
ReadOnlyProject的實作很簡單,請參考圖4。它直接繼承Project,然後覆寫所有會改變狀態的methods,直接丟出UnsupportedOperationException。
▲圖4:ReadOnlyProject程式碼(部分)
Project與Task都需要一個唯讀版本,重構後的領域模型現在有8個類別,請參考圖5。
▲圖5:領域模型成長到8個類別
***
下集預告
經過一番努力,重構至此領域模型終於有物件導向領域模型的樣子。但是一開始看起來很「礙眼」的TaskList程式依然沒變,還是原本那個150行、看起來亂亂的樣子。沒關係,下一集Teddy再來對付它。
***
友藏內心獨白:重構也要價值驅動。
2024-09-16 20:39:00
September 16 17:48~18:40;19:49~20:39
▲從下層爬往上層的咪咪 (by 常玉)
工商服務
想了解本系列文章完整內容,請參考【重構既有系統:邁向整潔架構實作班】。課程介紹與報名網址在此:https://teddysoft.tw/courses/refactor-to-ca/。
***
前言
分層原則
***
友藏內心獨白:只有一個物件變成全村的希望,這樣壓力太大。
2024-09-13 22:06:00
September 13 20:30~22:07
前言
上一集<重構既有系統,邁向整潔架構 (2):逆轉重構流程>Teddy提到只要「大膽假設」不管你是開發什麼系統,就是要套用Clean Architecture就對了。如此一來,便可將重構從「由下而上」的設計方法轉變成「由上而下」的設計過程。
接下來這幾集,Teddy將以Task List Kata為例,說明如何將既有系統的架構重構成Clean Architecture。
***
Task List Kata介紹
Task List Kata是一個公開的重構練習,程式碼可參考:https://kata-log.rocks/task-list-kata。如圖1所示,它有8種不同的語言版本,Teddy使用Java版本。
▲圖1:Task List Kata支援的語言
如圖2所示,Teddy將Task List Kata複製到自己的repository,並將package name改成tw.teddysoft.tasks。
▲圖2:Task List Kata專案目錄
Task List Kata原本只有三支程:
▲圖3:Task.java程式碼
▲圖4:TaskList.java程式碼, 1/2
▲圖5:TaskList.java程式碼, 2/2
***
鄉民們怎麼重構
首先,了解需求。Task List Taka的介紹網頁提到 (用ChatGPT翻譯成中文):
***
任務清單
這是一個對基元(primitives)過度依賴的程式範例。
基元是任何具有技術性質且與您的業務領域無關的概念。這包括整數、字符、字串和集合(列表、集合、映射等),還有執行緒、讀取器、寫入器、解析器、異常處理等任何純粹專注於技術問題的事物。相比之下,這個專案中的業務概念,如「任務」、「專案」等,應被視為您領域模型的一部分。領域模型是您所運營的業務的語言,將其應用於代碼庫有助於避免使用不同的語言,從而幫助避免誤解。根據我們的經驗,誤解是造成漏洞的最大原因。
練習
嘗試實現以下功能,同時逐步重構以去除基元。在完全重構代碼以移除基元之前,儘量不要實現任何新行為,也就是說,只有在您即將更改的代碼已被重構後,才進行變更。不要重構無關的代碼。
一組判斷基元是否已移除的標準是,只允許基元出現在構造函數的參數列表、本地變量和私有字段中。基元不應該被傳遞給方法或從方法中返回。唯一的例外是基礎設施代碼——與終端、網絡、資料庫等進行通信的代碼。基礎設施需要將數據序列化為基元,但應視為特殊情況處理。您甚至可以將基礎設施視為一個獨立的領域,具有技術性質,其中基元是該領域的核心概念。
您應該嘗試將測試包圍在您正在重構的行為周圍。一開始,這些測試大多是高級系統測試,但隨著進展,您應該會撰寫更多的單元測試。
***
上述說明已經提示了重構方向:「優先考慮去除primitives」,也就是去除重構中提到Primitive Obsession(基本型別依戀)壞味道。但是一般人看到這個練習,直覺的想法是:「把大的拆成小的」。很多人會先把TaskList的private methods像是show、add、setDone、help、error等「座艙升等」,各自變成一個class,並套用Command設計模式,讓execute可以執行不同的命令。
接著,為了產生這些不同的命令,又套了Simple Factory。然後呢…….嗯,就沒有然後了。此時TaskList程式長度剩下原本的1/3,抽離出來的Command也符合單一責任原則,程式碼也很短,感覺好像沒什麼明顯地方需要重構了。
***
換你想
在正式談Teddy如何重構Task List Kata之前,請鄉民們也想想看,如果是你,你會如何重構?
***
工商服務
想了解本系列文章完整內容,請參考【重構既有系統:邁向整潔架構實作班】。課程介紹與報名網址在此:https://teddysoft.tw/courses/refactor-to-ca/。
***
友藏內心獨白:搞錯順序只會徒然浪費時間。
2024-09-10 11:49:00
September 10 07:38~08:08;11:30~11:49
▲圖1:Ada逆練九陰真經
前言
上一集<重構既有系統,邁向整潔架構 (1):為什麼透過重構改善軟體架構很困難>Teddy提到重構是一種「由下而上」的設計,而設計本身應該採用「由上而下」的過程,這也是為什麼透過重構改善軟體架構會如此困難的原因。
為了簡化重構軟體架構的難度,要想辦法將重構流程改成「由上而下」。
***
怎麼做?
要「逆轉重構流程」,其實很簡單,就是先確定架構目標就可以了。例如,假設鄉民是Uncle Bob的粉絲,不管手邊有什麼軟體,就是要想辦法無條件套用Clean Architecture就對了。如此一來,你就有了軟體架構重構的目標:Clean Architecture。
現在Teddy把架構重構的問題轉換成如何套用Clean Architecture,Teddy用Pattern Language的方式來解釋這個套用的過程,請參考圖1。有五個模式支撐Clean Architecture:
▲圖1:Clean Architecture底下有五個模式
上述五個模式的套用順序,先從Four Layers開始,將系統分成以下四層(四個不同的packages):
有了大方向之後,接下來就是把你的既有系統想辦法塞入Clean Architecture這四層之中,如圖2。
▲圖2:Clean Architecture的四個階層
***
下一集Teddy將會以Task List Kata為例,說明如何將既有系統的架構重構成Clean Architecture。
***
工商服務
想了解本系列文章完整內容,請參考【重構既有系統:邁向整潔架構實作班】。課程介紹與報名網址在此:https://teddysoft.tw/courses/refactor-to-ca/。
***
友藏內心獨白:道理很簡單,要做到卻不簡單。