1

該不該使用Unit of Work和Repository?

 9 months ago
source link: https://teddy-chen-tw.blogspot.com/2023/06/unit-of-workrepository.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

該不該使用Unit of Work和Repository?

June 12 15:48~16:20;20:22~22:56

截圖 2023-06-12 下午8.35.05

▲圖1:ezKanban的Repository介面


前言

昨天提到6/5~6/7到客戶家上【領域驅動設計與簡潔架構入門實作班】,有一位學員問Teddy的三個問題:

  1. 為什麼Teddy建議Entities Layer的物件不要直接操作Repository?
  2. 為什麼有人說UoW(Unit of Work)和Repository在DDD裡面算是Anti-Pattern?
  3. 為什麼Teddy沒有用Specification模式?

昨天談了第一個問題,今天聊聊第二個問題:「在DDD中,UoW和Repository要不要使用」?

Unit of Work(UoW)

Unit of Work和Repository這兩個設計模式都出自於Martin Fowler所寫的《Patterns of Enterprise Application Architecture》。Unit of Work顧名思義就是工作單元,什麼叫做工作單元?就是把一連串的工作步驟,視為「一個單元」、「一整包完整的大步驟」。一個工作單元隱含一個交易邊界(transaction boundary)

舉個例子,在ezKanban裡面,有以下幾個使用案例:

  • CreateBoardUseCase
  • CreateWorkflowUseCase
  • CreateStageUseCase
  • CreateCardUseCase

每一個使用案例,都是一個工作單元,形成一個交易邊界。產生一個Board是一個工作單元,要嘛成功,要嘛失敗。同理,產生Workflow、產生Stage、產生Card,都是一個工作單元。很簡單,對不對!

ezKanban團隊使用ezKanban的領域模型開發了看板桌遊,這是另一個Bounded Context。在看板桌遊中,有一個CreateKanbanGameUseCase用來產生新的看板遊戲。這個使用案例的實作,呼叫上述四個使用案例。現在問題來了:「在看板桌遊的Bounded Context中,CreateKanbanGameUseCase是一個工作單元,它的執行要嘛成功要嘛失敗。但是因為CreateKanbanGameUseCase的實作方式是重複使用CreateBoardUseCase、CreateWorkflowUseCase、CreateStageUseCase與CreateCardUseCase,這四個使用案例各自是一個工作單元,要怎麼把它們用另一個更大的工作單元包起來?」

Unit of Work設計模式就是要解決這個問題,簡單講,就是把transaction manager注入給使用案例,而不是讓使用案例自己去控制。如此一來,最外層的使用案例負責控制transaction的開始與結束,內部的使用案例只是接受這個由最外層使用案例所注入的transaction manager。如此一來,便可以因應不同使用情境(Context)的需要,動態決定工作單元的範圍。

為什麼不要使用Unit of Work?

如果不管DDD,Unit of Work是一個很棒的設計模式。但是,在DDD中,Aggregate已經形成了一個交易邊界。如果在DDD中需要使用Unit of Work,則代表在某個Context底下,需要把好幾個不同的Aggregate放在同一個交易中。這不就和原本在DDD中「Aggregate形成了交易邊界」互相衝突了。

更進一步來看,在DDD中,Aggregate由Repository負責儲存與讀取。而「理論上」一個Repository可以各自採用不同的資料庫來儲存Aggregate。也就是說,如果你願意,可以將Aggregate當成一個微服務來佈署。如果在DDD中使用Unit of Work,則這些被放在同一個Unit of Work的Aggregate,就代表它們要綁在同一個資料庫中(除非使用distributed transaction,但採用這種做法的人很少,因為會造成效能問題),這就造成不同的Aggregate透過資料庫產生耦合。因此,Teddy覺得在DDD中,不應該使用Unit of Work。

Repository可以用嗎?

在Martin Fowler的《Patterns of Enterprise Application Architecture》書中,Repository代表Collection-Based的儲存體。也就是說,只要從Repository拿出物件,之後對於該物件的修改,會直接反應回Repository,使用者不需要呼叫save方法來儲存該物件。

在Vaughn Vernon所寫的《Implementing Domain-Driven Design》,進一步將Repository的實作分成Collection-Based Repository與 Curd-Based Repository。圖1為ezKanban所設計的Repository介面,採用Crud-Based Repository。ezKanban的所有Aggregate所對應的Repository都是採用相同的介面,只有findById, save與delete這三個方法。

不管是Collection-Based或是Crud-Based,Teddy主張,只要固定Repository介面,將其限制在單一Aggregate的新增、修改、刪除、查詢,這樣子使用Repository並不會有什麼太大的問題。

但是,實務上經常可以看到,很多開發人員在Repository身上加了很多查詢方法。如此一來,雖著需求演進,Repository的介面越來越肥大。你可以說,這種使用Repository的方式,違反了單一責任原則、開放封閉原則,以及介面隔離原則。

所以,只要固定Repository介面,將其餘查詢方法另外設計(在ezKanban中採用Inquiry設計模式來解決這個問題),在DDD中使用Repository是沒有問題的。

結論

以上,是Teddy近幾年開發ezKanban所累積的經驗。Unit of Work比較簡單,ezKanban壓根就沒使用過它。但是,針對Repository的使用方法ezKanban團隊重構了好幾次。一開始Teddy也是在不同的Concreate Repository中直接新增個別Aggregate所需要的查詢介面。但隨著系統越來越複雜,Repository也變得越來越亂,不容易理解其中的邏輯。後來,套用CQRS之後,把查詢、命令分離,保留最簡單的Repository介面。如此一來,使用Repository就沒有問題了。

友藏內心獨白:不是不好用,是你不會用 XD。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK