走過完整的「申請獎學金」案例後,會有一種感覺:「好像太麻煩了!」每個指令都這樣設計確實會多做不少額外工作。複雜的運算還能接受,但有時前端只是想要一些資料來顯示,也得分這麼多層,這種 overhead 真的值得嗎?有沒有比較輕量級的做法?

有的。本章來談 CQRS(Command Query Responsibility Segregation),中文常譯為「命令查詢職責分離模式」。

命令查詢職責分離模式#

Ajay Kumar 在 2019 年的 CQRS 一書中建議我們把對系統的所有指令分成兩類:

  • 查詢(Query):不會改變系統狀態。
  • 命令(Command):會改變系統狀態。

以教務處網站為例,「獎學金列表」是查詢,「申請獎學金」是命令。

Ajay Kumar 提醒:查詢不會改變系統狀態,且經常會變動,因此不需要勞煩 Entity 與 Service 出動。Controller 直接呼叫特定的 Repository 去查就好。除了實作較簡單,因 Entity 沒有參與流程,前端需求變動時也不會牽動到核心 Entity。

CQRS 的副作用:假性重複#

Robert C. Martin(鮑伯大叔)曾提醒我們要分辨「真正的重複」與「假性重複」。

重複之所以惹人厭,是因為兩個物件長得一模一樣時,需求變動就得兩邊一起改。但如果這兩個物件雖然長得像,卻代表不同的東西,而且也不會一起變動呢?這就是假性重複。

在 Clean Architecture(簡潔架構)系統中同時導入 CQRS 就會出現這種情況。Query 流程不走 Service,Controller 會請 Repository 回傳一個「為這個 Query 客製化」的物件,姑且稱之為 VO。VO 在系統開發初期可能跟 Entity 長得幾乎一樣,例如撈取獎學金列表時。

此時必須忍住,不要直接拿 Entity 回傳。原因在於:

  • 直接回 Entity 違反 Clean Architecture 的跨層原則。
  • 更糟的是會讓系統的核心 Entity 隨著最外層的 UI 快速變化,但 UI 變化本來就不該是核心關心的事。

於是同時為這個 Query 客製化一個 VO,看起來就出現了與 Entity 的重複。

鮑伯大叔的建議是:VO 與 Entity 長得像、或不同 Query 的客製化 VO 互相長得像,都屬於假性重複。這種重複不致命,放著不處理,隨著 UI 慢慢演進,這些 VO 自己會各自演化,最後很可能會長成完全不一樣。如果硬著頭皮去消除這種重複,反而會阻礙各物件的自然演化,造成日後維護的困難。

於是我們可以放心:既然無害,就讓它各自演化吧。

原文出處#

  • 原書/iThome:https://ithelp.ithome.com.tw/articles/10273154