在日常工作中,你是否也遇到過下面幾種情況:
為什么會出現這種現象?其本質仍舊是代碼組織結構不合理,我們將不同的復雜性揉在一起,從而造成了更大的復雜性,然后如此往復,不知不覺中陷入巨大的復雜性旋渦不可自拔。
CQRS 是 Command Query Responsibility Segregation 得簡稱,簡單理解就是對 “寫”(Command) 和 “讀” (Query)操作進行分離。反應快的同學會說:“也不是什么高深技術嗎,不就是數據庫的讀寫分離嗎?”
是的,數據庫的讀寫分離也算是一種 CQRS,但 CQRS 的含義要比這復雜的多。
CRQS 既是一種流行的業務架構,又是一種設計思維。
CQRS 的核心是“拆分”,將復雜系統拆分為 Command 和 Query 兩個部分,針對不同的場景使用不同的模式,選擇最合適的技術落地最佳解決方案,避免兩者相互掣肘相互影響。
CQRS的目的是降低整個系統的復雜性,那它背后的邏輯是什么?
假設,在一個系統中:
如果使用同一套模型來處理 Command 和 Query,那在極端情況下,系統的復雜性為 M * N,因為兩者相互影響,調整一方的同時要時刻關注對另一方的影響。
圖片
這種“你中有我,我中有你”的設計方式,“兩者的相互影響”成為系統最為復雜之處,大量精力消耗在“排查影響”,而非最有價值的設計和編碼。
如果,將 Command 和 Query 徹底分離,系統的復雜性變成 M + N。Command 的變更不會影響 Query,而 Query 的修改也不會影響 Command。
圖片
當然,以上兩個極端在實際工作中也很少見,通常系統的復雜性介于兩者之間。
圖片
這只是從理論進行推導,在實際工作中隨處可見的“沖突”也是對“拆分”的一種暗示。
以最常見的分層架構進行介紹,具體如下:
圖片
如圖所示,將系統分成5層,每層的含義如下:
其實,分層架構本身也是一種“拆分”,將不同的關注點封裝在不同的層次。但除了橫向分層,還可以基于 CQRS 對其進行縱向拆分,也就是將每個層的組件拆分為 Command 和 Query 兩部分。
由于接入層沖突較小,本身拆分的意義不大,在此不做要求,但從嚴格意義上講,仍舊建議進行拆分。
應用服務層拆分就是將一個應用服務拆分為 CommandService 和 QueryService 兩組。
圖片
這樣做可以避免很多不必要的麻煩,Command 和 Query 存在較大的區別,具體如下:
CommandService | QueryService | |
依賴組件不同 | ValidateService 驗證服務;LazyLoaderFactory 延遲加載服務;CommandRepository 不帶緩存的倉庫;EventPublisher 事件發表器 | QueryRepository 帶緩存功能的倉庫;JoinService 數據聚合服務;Converter 數據轉換服務 |
核心流程不同 | 驗證、加載、業務操作、同步、發布事件 | 驗證、加載、數據組裝、轉換 |
功能加強不同 | 主要是事務管理器 | 主要是緩存組件 |
回想開篇時提到的場景,完成應用層拆分,就不在為使用錯組件而煩惱:
除此之外,針對統一的操作流程,還可以進一步抽象來消除重復的“模板代碼”,比如:
抽象出 BaseCommandService 和 BaseQueryService 兩個父類用于統一核心流程
子類實現 BaseCommandService 和 BaseQueryService 的抽象方法完成功能擴展
模型層是系統的核心,它的設計直接影響整個系統的質量。作為承接業務邏輯的核心,比較流程的實現策略包括:
關于哪個才是最優解,網上已經爭論多年,最終也沒有結論。但我始終認為“沒有業務場景就討論方案,就是在耍流氓”。
從不同應用場景出發便可得到如下結論:
我經常說:“最簡單的“寫”也是復雜,最復雜的“讀”也是簡單”,其背后邏輯是基于對 Command 和 Query 的場景判斷。
將模型拆分為 Command 和 Query,具體如下:
圖片
完成模型拆分后,新模型具有以下特征:
這塊是拆分的重點,為了方便理解,簡單舉個例子:
比如在電商的訂單模塊:
如果讓一個模型同時支持著三個場景,那模型自己就變的非常復雜,很難判斷某個方法、某個字段究竟屬于哪個場景。
此時,應該根據場景對模型進行拆分:
三個模型相互獨立,互不影響。
當然,由于使用統一的 Repository 還需提供對應 VO 的 Converter:
倉庫層拆分也是非常有必要的,在這一層主要有幾項沖突:
CommandRepository | QueryRepository | |
底層實現不同 | 主要基于 DB 實現 | 基于 DB、Redis、ES 等多種存儲引擎 |
方法復雜性不同 | 提供僅有的少量方法并足以支持大多數場景,比如 save、update、getById 等 | 根據業務場景進行定制,方法多種多樣(單條、批量、分頁、排序、統計等),維度多種多樣(id、user、status) |
返回值不同 | 直接返回裝配完整的富對象 | 根據業務場景定制返回值 |
倉庫拆分后整體架構如下:
圖片
倉庫拆分具有以下特點:
數據層拆分是最重要的拆分,提到分離第一反應也是“數據庫主從分離”。
數據層拆分的本質是:各種數據存儲引擎的最佳應用場景相差巨大,讀 和 寫 優化往往存在矛盾。
仍舊以最常見的數據庫為例:
魚和熊掌不可兼得,在數據庫層展示的淋漓盡致!
數據層拆分后架構如下:
圖片
該模型具有以下特點:
數據層拆分是大型系統最終的歸宿,仍舊以訂單系統為例:
這就是我們面臨的現狀:“數據密集型系統”越來越多的應用程序有著各種嚴格而廣泛的要求,單個工具不足以滿足所有的數據處理和存儲需求。取而代之的是,總體工作被拆分成一系列能被單個工具高效完成的任務,并通過應用代碼將它們縫合起來,通過 API 的方式,對外提供服務,屏蔽內部的復雜性。
“拆分”是“分離關注點”的重要手段之一。拆分的目的是將問題進行歸類,然后采取有針對性的手段更好的解決問題。
CQRS 作為一種架構,將業務系統不同部分進行歸類,接下來需要為 Command 和 Query 尋找最優解決方案:
聚合設計
倉庫設計
LazyLoad + Context 模式
業務驗證
領域事件
…
本文鏈接:http://www.massagepornsite.com/showinfo-26-6217-0.htmlDDD 與 CQRS 才是黃金組合
聲明:本網頁內容旨在傳播知識,若有侵權等問題請及時與本網聯系,我們將在第一時間刪除處理。郵件:2376512515@qq.com
下一篇: 資損防控技術體系簡介及實踐