May 10, 2021
Here is the article.
在做平台的促銷活動時, 總是想要查詢任何一個商品的歷史狀態資料, 而且任何時間都能生成統計數據. 想要查詢上個月份、上一季或者過去任何時間的彙總.
一些7788的表示方式查詢方式...(客戶跟PM的需求, 我們總是難以滿足)
有些查詢方式, 關聯式資料庫查詢起來不太高效.
有些查詢方式, NoSQL也不好查.
有的還要查詢稽核日誌或者某一隻股票的變化歷程等等的...
一般作法如圖: 參考連結
通常我們會把對model做CRUD, 並且儲存在資料庫內.
但隨著業務越來越複雜時, 就引入分層模式.
開始讀取出來是DAO, 在業務邏輯層變成DTO, 再給表達層...
為的是什麼?
因為表達層所要的展示方式已經跟當初寫入在資料庫的模型有所不同了.
為了使用多種模型來表示相同的數據.
再來這樣的模式也導致一些問題.
在相同的資料上一起執行讀寫作業時, 就有鎖的問題了(事務隔離).
有的還要排序、分頁...
使用ORM又Preload很多資料為了提高吞吐量, 但卻佔用了API服務的大量記憶體.
如果查詢很複雜跨很多表, 就出現大量Join...性能就下降.
為了讓讀、寫分開. 有的組織對資料庫做了讀寫分離架構.
讓Read命令去副本讀取, CUD命令去主庫寫入操作.
但這樣依然無法解決多種表達層模型還有複雜Join的問題.
CQRS
CQRS(Command Query Responsibility Segregation)
Martin Fowler在2011年部落格上曾經介紹了CQRS(命令查詢職責分離), 是一種基於讀寫分離思想的架構模式.
把一個系統分成寫入命令, 跟查詢指令兩部份.
寫入命令, 當收到CUD請求時, 將之轉成Domain Event, 並寫到Event Store.
讀取指的就是表達層所需要的資料
事件傳遞的方式, 常見的還是用之前提到的MQ
Components
Command Handler: 就是實際的業務邏輯, 驗證, 或是處理Request Body, 然後也負責轉成Event, 寫入DB, 推送到MQ
Event Handler: 這裡就是負責訂閱特定地Domain Event, 然後轉成查詢時需要的DTO寫到適當的儲存中(RDBMS、Mongo、ES...etc)
Facade Pattern: 提供一組抽象的API接口, 給表達層用
Read Layer: 就負責查詢儲存, 並且組合
pros
寫入性能更好(畢竟讀寫算是分離了)
讀取性能更好(因為會用適當的儲存做查詢)
歷史紀錄追蹤
滿足單一職責, 因為都滿足了對各自的業務做封裝
兩邊允許各自擴展
查詢端的儲存已經是存查詢所返回的DTO了, 不必再做Model->DTO的轉換
cons
架構複雜
資料同步需要一些時間, 但保障最終一致性
很需要DDD的相關領域知識
適用情境
- 當寫入命令的模型跟讀取模型的差異很大時
- 希望系統查詢跟寫入分開優化演化
- 希望系統同時滿足高併發寫、高併發讀取時, 且允許資料最終一致性
Aggregate
這名詞來自於DDD, 指的是Entity和Value Object的組合.
它定義了一個邊界, 在這邊界裡, 資料必須保證資料完整性.
Aggregate有一個Root Entity, 可以從這Root去獲取該邊界內的其他entity和value object.
如果資料庫有做水平分片(sharding), 那同一個聚合內的entity是在同一個分片內.
這裡的Aggregates之間主要是透過標示符號(例如資料庫的PK), 而不是直接對象引用.
像是Order聚合內有UserID, 透過UserID去關聯到User聚合.
參考1
參考2
Greg Young文章
Martin Fowler文章
PS: CQRS不是銀彈...適用的場景不多, 若硬要套用, 只能默哀3秒鐘先.
No comments:
Post a Comment