iT邦幫忙

2021 iThome 鐵人賽

DAY 27
2

大家有看過以前鄉下那種燒柴的大灶嗎?


圖片截自爆廢公社

記得以前小時候,阿公阿嬤家有這種燒柴的大灶,阿公下田幹活時,阿嬤就去到處撿柴回來堆在院子裡,晚上要洗澡時就燒熱水來用。那時家裡雖然已經有熱水器,但阿嬤還是每天堅持用大灶燒水,問她為什麼不用熱水器就好,「安呢卡省啦!」她總是簡單這麼回答著,直到後來年紀漸漸大了,開始抬不太動大鍋水了,她才慢慢不再堅持,開始用熱水器洗澡。

在用一個「申請獎學金」的範例看完一次完整的指令以後,你應該也會覺得:「蛤!好麻煩喔!」

是的,如果每個指令都這樣設計,的確是會做不少額外的工作。複雜運算就算了,但有時候,前端就只是想跟後端要一些資料來顯示,也要分這麼多層,這令我們不禁擔心,這種 overhead 真的值得嗎?這種情況下,是否有比較「省事」、比較「輕量級」的做法?

有喔!

今天我們就來聊聊「命令查詢職責分離模式」(CQRS - Command Query Responsibility Segregation)。

命令查詢職責分離模式

Ajay Kumar 在 2019 年出的 CQRS 一書中,建議我們把所有對系統的指令,分成不會改變系統狀態的「查詢(Query)」與會改變系統狀態的「命令(Command)」。以我們的教務處網站系統為例,「獎學金列表」就是一種查詢,「申請獎學金」就是一種命令。

Ajay Kumar 告訴我們,查詢指令不會改變系統狀態,而且經常會改動,所以不需要勞煩 Entity 與 Service 出動,只要叫 Controller 直接呼叫 特定的 Repository 去查就好。除了比較簡單以外,因為 Entity 沒有參與流程,所有前端需求的變動,也不會動到系統核心的 Entity 設計。

CQRS 的副作用:假性重複

Uncle Bob 曾提醒我們,要分辨真正的重複與假性重複。什麼意思?不是說重複是萬惡之淵,必除之而後快嗎?事到如今怎麼突然冒出了個什麼假性的重複,是這搞啥鬼?

這時,就得回頭看看我們講的「重複」到底壞在哪裡了。重複之所以討厭,在於一旦兩個相異物件長得一模一樣,當需求有改,兩個得一起改。那如果這兩個物件雖然長一樣,但不只代表不同東西,而且也不會一起改呢?

胡說!哪有這種東西?

有的,在 Clean Architecture 的系統中,同時套用 CQRS 就會。

在 Query 時,因為不用走進 Service,這時 Controller 會跟 Repository 要求一個特製的物件回傳格式,在不失語意的假設下,我們先稱之為 VO。VO 與 Entity 在系統開發初期可能長得很像,譬如撈取獎學金列表時,Controller 很可能需要撈出跟 Entity 一模一樣的東西。

這時,根據 Ajay Kumar 的建議,要忍住,不可以直接拿 Entity 去回傳,因為我們一直強調的,前端畫面變動得很快,如果此時 Controller 回的是 Entity,不但違反了 Clean Architecture 的跨層原則,更糟糕的是,它讓系統的核心 Entity 隨著最外層的 UI 快速變化,但,你知道的, UI 的變化根本不是核心該管的事情,所以,這時要回一個「為這個 Query 客製化的 VO」。

於是重複就出現了。這使我們陷入進退維谷的窘境,這該如何是好?好佳在,Uncle Bob 這時跳出來解救我們了。他告訴我們,VO 與 Entity 長得像,或是不同 Query 的客製化 VO 長得像,是一種「假性重複」,這種重複不只不會致命,你放著不處理,隨著 UI 慢慢改變,這些 VO 自己會各自演化,最終很有可能也會長得不一樣,如果當初硬著頭皮去消重複,反而會阻礙這種自然演化,造成維護上的困難。

於是我們就放心了,既然無害,那就隨它去吧!

謎之聲:「車過橋頭往岡山,船到橋頭至楠梓。」


圖片截自 YouTube

Reference

  1. Ajay Kumar, CQRS (Command Query Responsibility Segregation), Independently Published, 2019
  2. Z-xuan Hong,看到重複就刪除? 小心不要看到黑影就開槍:https://tinyurl.com/5fvwf6ya
tags: ithelp2021

上一篇
Day 26 「一個巨星的誕生」Entity、Repository 與單元測試
下一篇
Day 28 「最好避免犯錯的方法」單元測試與 GitFlow、主線開發
系列文
你就是都不寫測試才會沒時間:Kuma 的 30 天 Unit Test 手把手教學,從理論到實戰 (Java 篇)30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中
1
伊恩
iT邦新手 4 級 ‧ 2021-09-28 17:53:36

文中的 VO,看起來是指 DDD 中提到的 Value Object?

Kuma iT邦新手 3 級 ‧ 2021-09-28 17:56:33 檢舉

某種程度上可以這麼理解,但是因為我們這裡並沒有要討論 DDD 所以我刻意不去定義它

但是,是的,你可以這麼理解 :)

0
deh
iT邦研究生 1 級 ‧ 2021-12-17 15:54:24

VO繼承Entity,有需要再用介面限制不就可以避免重複了?

看更多先前的回應...收起先前的回應...
Kuma iT邦新手 3 級 ‧ 2021-12-17 17:40:37 檢舉

當然可以,沒有問題!程式是自由的 :)

只是 CQRS 建議我們在 Query 的過程中不需要進入 DDD 的模型而已。

但是,程式是自由的,您要這麼做當然可以,誰規定一定要做整套的 CQRS 呢?

Kuma iT邦新手 3 級 ‧ 2021-12-18 00:51:02 檢舉

不過,繼承雖方便,但卻容易造成一些維護與理解上麻煩的地方,所以不管要不要使用 CQRS,在繼承前還是要慎重一點。

例如, VO 是一種 Entity 嗎?如果不是,那其實一般也不適合繼承。

Kuma iT邦新手 3 級 ‧ 2021-12-18 00:53:48 檢舉

主要是閣下雖提出如何「避免重複」的建議,
但筆者原文中不但沒有要避免重複,
反而在說的當 Query 出現一些「假性重複」不用太擔心,可以不處理無妨 :)

deh iT邦研究生 1 級 ‧ 2021-12-20 09:33:14 檢舉

又想了想,大概了解了
如前文條件

這兩個物件雖然長一樣,但不只代表不同東西,而且也不會一起改

那本來就不該用繼承,確實不需要處理
感謝

0
paooap6365
iT邦新手 5 級 ‧ 2022-01-25 01:19:00

文中提及

分成會改變系統狀態的「查詢(Query)」與不會改變系統狀態的「命令(Command)」。

這段看來兩個寫反了。

我要留言

立即登入留言