iT邦幫忙

2024 iThome 鐵人賽

DAY 26
1
Software Development

一個好的系統之好維護基本篇 ( 馬克版 )系列 第 26

Day-26: CQRS ( Command Query Responsibility Segregation )

  • 分享至 

  • xImage
  •  

同步至 medium

咱們在看 Domain-Driven Design 的書或資料時,應該三不五十會看到 CQRS 這個東西,並且後來如果又單獨查了 CQRS 的東西,就又會有不同的用法,所以這裡我們就來理一下這些東西。


版本 1: Event Sourcing + CQRS 最常見的版本

https://ithelp.ithome.com.tw/upload/images/20241010/200893584dnLR9QLD2.png

上面這張架構圖基本上應該是我們最常看到的 Event Sourcing + CQRS 的架構圖,我先來簡單說一下它的流程 :

  1. client 發送一個 command 請求到 command service 去。
  2. command service 收到會透過 aggregate 產生 domain event,然後將它寫入到 event store db。
  3. 同時也會發送 domain event 到 event bus。
  4. 然後 query service 會監聽 domain event。
  5. query service 收到事件後寫入到 db 中。

然後接下來 query 的情境就幾乎和我們一般的使用情況差不多了。

然後這裡來說說它的起源與優缺。

最開始只有 Event Sourcing

Event Sourcing 嚴格來說是從 DDD 有後才比較有名的產物,它的概念就是 :

我們資料庫只存 Domain Event,而不是我們傳統的 Domain State ( 就是資料庫存當下狀態 )

因為在 DDD 中的 Aggregate 的變化都是會產生 Domain Event,所以我們是不是只有留下他就可以產出最後的 State 了 ?

那這樣有什麼好呢 ?

  1. 有所有事件的留存,所以代表可以回復到任一個時間點下的狀態。
  2. 這個我自已覺得對查問題有幫助,因為有了過去的狀態變化歷程 ( 但我腦袋在想 log 寫好不是也可以 ? )
  3. 減少狀態不同步的問題,如果 aggregate 是用 state 存,那這樣就要確保儲到資料庫與 Domain Event 是在同一個交易完成的,但是如果用 event sourcing 就不用。
  4. 減少 race condition 因為所有的事件都是新增,不會有修改這事情 ( 但還是要想辦法處理 duplciate event,好像是會用樂觀鎖來處理 )。

備註:
第 3 點可以用我們之前談過的 transactional outbox 來處理。
Day-23: Domain Event 之 Transactional OutBox 與 EventBus

那為什麼這裡會有 CQRS ?

因為每一次拿狀態都要抓一堆 Domain Event 來回復 State 啊,所以這裡通常會配 CQRS

我們大部份的產品服務正常情況下一定是 Query > Command 的情境,所以如果沒這樣用,那它應該早就因為 latency 太高,而且罵到爆了,或倒閉了。

這個版本有什麼毛

簡單的說要花很多時間處理,一切都是挑戰,但要想想你得到了什麼。

  • Write Service 那因為沒有儲 state 了,所以你當業務需要資料判斷時就會需要去 Read Service,那這個時間資料還沒同步怎麼辦呢 ?
  • 事件存儲的大資料處理能力。
  • 事件模型的變化,我們事件是會變化的,所以在 Write Service 重播與 Query Service 的相容要如何處理也是需要花心思處理的。
  • 資料搬遷要多花心力。
  • 傳統上的分散式事務處理可能要用其它方法來處理

版本 2. 軟體架構分開

這個也算是 CQRS 的應用一種,就是將模型分成 :

  • Write: 用 Domain-Driven Design。
  • Query: 以高性能 + UI 需求為主。

https://ithelp.ithome.com.tw/upload/images/20241010/20089358fabw8n6Kza.png

會有這個的原因在於 :

Aggregate Model 在 Query 情境下,很耗資源,而且那些欄位也不一定是 Query 需要的

然後這種模式還有個好處 :

  • 根據架構我們可以知道那些是 command、那些是 query 操作,不會有那種一個 query 然後偷偷在裡面新增什麼。
  • 兩個 model 分開來,不會因為 query 需要,而影響 aggregate 需要新增什麼欄位。

所以事實上我看過不少人在架構設計上都是用這種方式的,例如這個 youtube 中提到的架構,它也是在 application 那層分開來。

https://ithelp.ithome.com.tw/upload/images/20241010/20089358nxzujVGMcb.pnghttps://ithelp.ithome.com.tw/upload/images/20241010/20089358w7pNTvsabK.png

但這裡是有幾個東西要想一下 :

1. 資料庫要不要也用讀寫分離呢 ?

我自已覺得可以。

然後通常會碰到的第一個問題就是,如果我需要先讀資料來做判斷,但讀 read 那台會有延遲,要如何處理 ?
那就讀寫的那台 db,而且目前很多資料庫 libary 都也有支援讀寫分離。

2. Query Model 實際上是這麼樣 ?

我這裡想法是類似在程式碼中有個很像 aggregate 的 query model,然後它會對映到 db 的 view,因為這樣就可以透過 db 只抓 model 中需要的欄位,所以嚴格說來那個 query model 比較接近 scheam。

3. Write 與 Query model 有很多共同的東西呢 ?

可以使用 baseModel,但要記得那個 model 只能支援讀的東西,不然會讓 query model 變成可以修改東西的 model。

4. Query Model 如何產生呢 ?

就直接在 usecase 或 service 抓取那個 view 吧,但也不是說不能在 query 那裡多個 repository,但就只是它回傳的是 query model,但我自已比較少看過。


小結

在實務上大部份是以第二種為主,目前我真的比較少看過實際有使用 Event Sourcing + CQRS 來當一個公司的基礎架構上,有聽過的也只是當小專案實驗用,所以我沒很詳細說 Event Sourcing 也是這個原因。

這裡要不要使用就自已判斷吧,但如果是我應該不會將第一種方案當標配,基本上會從第二種方案來開始考慮。


上一篇
Day-25: 如何設計與管理 Bounded Context
下一篇
Day-27: 如何降低 Query 複雜性的探索
系列文
一個好的系統之好維護基本篇 ( 馬克版 )30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言