l

2021年9月9日 星期四

領域驅動設計學習筆記(21):貧血模型與充血模型(上)

September 09 07:29~8:30

▲Pascal這種身材應該算是充血模型,把房子都壓垮了還不充血嗎 XD


緣起

幾天前在臉書DDD台灣社團看到有人問「貧血模型」與「充血模型」的問題,問題大意如下:「假設有一個Product Entity,針對該Entity實作查詢產品功能。」

方法一:將查詢功能做在Product身上

public class Product {

private final ProductRepository repository;

public Product (ProductRepository repository) {

    this.repository = repository;

}

public Optional<Product> getProductByName(String productName) {

      return repository.findByName(productName);

}

}

***

方法二:將查詢功能做在Use Case身上

public class GetProductUseCase {

private ProductRepository repository;

public GetProductUseCase (ProductRepository repository) {

    this.repository = repository;

}

public void execute(GetProductInput input, GetProductOutout output) {

      output.setProdeuct(repository.findByName(input.getProductName()));

}

}

***

發問者認為:

  • 方法一的Product有getProductByName這個「行為」,感覺符合充血模型(Rich Domain Model)的定義。但是,讓Product直接耦合Repository好像又不太對。
  • 方法二將getProductByName從Product身上拔除,升等為GetProductUseCase,拿掉Product對Repository的依賴。但是,如此一來Product身上就光溜溜沒有行為,變成Martin Fowler所說的貧血模型(Anemic Domain Model)

到底要怎麼做比較好?

***

不是貧血、充血的問題

關於上述問題,Teddy覺得發問者所舉的例子本質上和Product Entity屬於充血模型或是貧血模型並無直接關係,而是和CQRS(Command Query Responsibility Segregation;命令查詢職責分離)比較有關。

在方法一的所謂充血模型範例中,Product身上的getProductByName(String productName)方法,是一個Query(查詢,回傳資料但不會改變系統狀態的操作),它本來就不需要也不應該存在Product身上。從CQRS的角度來看,Entity主要是要表達Command(命令,會改變系統狀態但不會回傳值的操作),因為發問者把Command與Query耦合在Entity身上,才會有「將getProductByName放在Product身上讓Product產生對Repository的依賴」的困擾。

方法二將getProductByName升級成GetProductUseCase,它現在變成一個代表Query的使用案例,如此一來實現了CQRS。把getProductByName從Product身上拔掉,並不會讓Product變成貧血模式,反而可以讓Product專心去負責原本應該表達的「Command Model(或稱為Write Model)」。這才是DDD(領域驅動設計)要套用的地方,應用在Write Model,而不是在Read Model。

在發問者所舉的例子中,Product就只有Query而有沒任何Command,所以將Product的Query拔掉之後,Product身上就沒有其他行為了,才會誤認此時的Product變成了貧血模型。

***

本來無一物

以上情境,讓Teddy想起有一次聽 Kevlin Henney 的演講,他提到一個故事(以下為Google翻譯後經Teddy修改過的中文):.

尊貴的 Qc Na 大師與他的學生 Anton 同行。Anton希望能引起師父的討論,他說:「師父,我聽說物件是很好的東西——這是真的嗎?」Qc Na憐憫地看著自己的學生,答道:「笨學生——物件只是窮人的閉包(closures)。」

被打槍之後,Anton離開了他的師父,回到了他的小研究室,打算研究閉包。他仔細閱讀了整個「Lambda:The Ultimate...」系列論文及其同類論文,並實作了一個帶有基於閉包(closure-based)的物件系統的小型 Scheme 直譯器。他學到了很多東西,期待著告訴師父關於他的進步。

在與 Qc Na 的下一次散步中,Anton 試圖給他的師父留下深刻印象,他說:「師父,我仔細研究了這件事,現在明白物件確實是窮人的閉包。」 Qc Na 的回應是用棍子打Anton,說:「你什麼時候才學得會?閉包是窮人的物件。」

那一刻,Anton開悟了。

Object is a poor man’s closure, and closure is a poor man’s object.

***

下集待續

先講完背景故事,下一集Teddy從DCI(Data, Context, Interaction)的角度,再談貧血模型與充血模型的不同觀點。

***

友藏內心獨白:貧血模型是富人的充血模型,充血模型是富人的貧血模型 。

1 則留言: