認知篇:CQRS架構模式的本質

来源:https://www.cnblogs.com/Jcloud/archive/2023/01/30/17074584.html
-Advertisement-
Play Games

CQRS只是一種非常簡單的模式(pattern),CQRS本身並不是一種架構風格,和最終一致性/消息/讀寫分離/事件溯源/DDD等沒有必然的聯繫,它最大優勢是給我們帶來更多的架構屬性選擇 ...


作者:京東科技 倪新明

CQRS只是一種非常簡單的模式(pattern),CQRS本身並不是一種架構風格,和最終一致性/消息/讀寫分離/事件溯源/DDD等沒有必然的聯繫,它最大優勢是給我們帶來更多的架構屬性選擇

1 CQRS 本質

1.1 CQS:命令和查詢分離

命令和查詢分離,Command and Query Segregation,其核心思想是在任何一個對象的方法可以劃分為兩類

•查詢:獲取數據,返回查詢數據,但不改變數據狀態 •命令:改變數據狀態,不返回任何數據

基於CQS的思想,任何一個方法都可以拆分為命令和查詢兩部分:

private int origin = 0;
private int add(int value)
{
    origin += value;
    return origin;
}

上述方法既改變了數據,又返回了數據狀態,如果按照CQS的思想,則該方法可以拆成Command和Query兩部分,如下:

private void add(int value)
{
    origin += value;
}
private int queryValue()
{
    return origin;
}

是否嚴格遵循上述約定存在爭議,對於命令側是否返回數據實際業務訴求中並不一定能夠完全統一。比如:

•"出棧" 操作同時改變棧狀態和返回數據 •某些業務場景下可能會有返回業務主鍵的訴求,比如下單操作返回訂單號

1.2 CQRS:命令和查詢職責分離

Command and Query Responsibility Segregation,即命令查詢職責分離,由Greg Young提出 。CQRS在CQS基礎之上,將分離的級別從代碼方法級別擴展到對象級別。CQRS 模式的應用非常簡單,如下圖所示

 

 

假設我們的服務為 OrderService,在非CQRS模式下同時包含了查詢和更新服務介面:

public class OrderService {
   //  根據id查詢訂單
    Order getOrder(OrderId)
    // 查詢已支付訂單
    List<Order> getPayedOrders()
    // 下單
    void placeOrder(Order)
    // 取消訂單
    void cancelOrder(OrderId) 
}

應用CQRS模式之後的OrderService被拆分成了兩個介面,分別承擔查詢和寫職責:

/**
命令側服務
*/
public class OrderService {
    void placeOrder(PlaceOrderCommand command)
    void cancelOrder(CancelOrderCommand command)
}
/**
 查詢服務
*/
public class OrderQueryService{
    Order GetOrder(OrderId)
    List<Order> getPayedOrders()
}

以上這種簡單的分離就是CQRS模式的全部了,是不是非常簡單?確實,單純的看,CQRS的確就是這麼簡單。

CQRS最大優勢就是基於這種職責分離能帶給我們更多的架構屬性選擇

•“查詢” 和 “命令” 兩側進行獨立部署以獲取更好的伸縮性 •“查詢” 和 “命令” 兩側獨立架構設計 •“查詢” 和 “命令”兩側進行獨立數據模型設計

基於CQRS,我們可以衍生出更多的架構屬性,結合實際的業務場景,進行差異化的架構設計。

團隊引入CQRS模式之後,往往不僅僅是簡單的在類的職責層面對讀寫進行分離,一般會採用更為複雜的應用架構風格,如下是典型的CQRS架構風格:

 

 

•命令側:命令側引入命令匯流排以支持對不同命令的靈活路由;突出領域模型的應用 •查詢側:引入查詢匯流排對查詢請求進行路由;請求鏈路一般直接連接到存儲層,實現不同的定製化查詢需求

2 CQRS迷思

2.1 數據模型是否要分離

CQRS強調命令和查詢的職責分離,但在底層的數據模型層面,CQRS並沒有進行強制限定,即採用CQRS模式並沒有要求必須要進行數據模型的分離。是否要進行模型分離開發人員需要具體情況具體分析。

•分離模型:查詢側和寫側模型不互相干擾,各自在應用層的實現複雜度比較低。但由於模型的分離,命令側和查詢側的數據一致性需要納入考慮範圍 •不分離:不需要考慮數據一致性問題,但由於查詢側和寫側對模型的訴求可能不一致,模型的設計往往需要折衷考慮。

2.2 CQRS 和 消息模式

CQRS和消息模式沒有必然聯繫,落地CQRS 並不一定需要使用消息模式

 

 

如果我們採用了CQRS模式,但是命令和查詢兩側底層所依賴的數據模型並未分離,而是基於共用的數據存儲和數據模型,命令和查詢之間不需要額外的交互,命令側的數據更新對查詢側實時可見。在這種架構模式下,兩側基於共用的數據已經天然的集成在一起,不需要額外機制進行通信,自然也無需引入消息了。如果我們採用CQRS模式,並且命令和查詢兩側進行了數據模型的分離,二者各自依賴獨立的數據模型。同時,數據存儲也分開部署。命令側負責數據的更新,而查詢側只負責數據的查詢,如何將數據的更新及時同步到查詢側是需要解決的問題。在這種架構模式下,使用消息模式作為兩側的通信機制是個不錯的選擇,當然,這並不是唯一的選項。

2.3 CQRS 和 ES(Event Sourcing, 事件溯源)

ES 並不是一個新的概念,在最早的金融系統中就已經應用。要瞭解ES,我們需要先看看傳統的數據存儲。在傳統應用中,資料庫例如MySQL(假設存儲介質是資料庫,)中存儲的始終是數據的最新的狀態。例如我們對某條用戶的信息進行了多次的修改或編輯,然後保存將數據存儲到資料庫中。無論何時,資料庫中都會記錄最後的、最新的用戶狀態。我們只要根據id或其他信息查詢資料庫中相應的記錄就能獲取該用戶的最新信息。這是應用中典型的數據存儲特點。

當然,我們可以基於特定的數據模型設計以保存數據的更改記錄。

這種數據存儲模式的特點是簡單,不需要額外的維護複雜的設計,我們能夠非常容易的獲取最新的用戶信息。但是不幸的是,我們丟失了歷史信息,包括用戶的意圖信息。而這些信息則有助於我們進行數據回滾、用戶行為分析以及開發過程中的調試等等。

 

 

在ES模式下,資料庫中存儲的不在是數據最新狀態,而是數據的變更記錄,更官方的說法是 “事件(Event)”。資料庫中存儲的數據變化的事件流。我們基於事件流可以對最新狀態進行重建,同時也可以便捷的重現任何歷史節點數據。ES需要解決大量事件的存儲和高效的實例重建問題,後續單獨的文章再介紹ES。

2.4 CQRS 和 Eventual Consistency(最終一致性)

最終一致性也常常在服務之間引入,最終一致性的目的是為了提高擴展性和可用性。

CQRS和最終一致性同樣沒有必然的聯繫。往往採用CQRS後,查詢和命令兩側會採用獨立的數據模型,在這種架構模式下,命令側的數據變化後及時同步到查詢側,兩側數據並非實時,在一定的延時後兩側數據最終達成一致。

3 結語

CQRS的最大優勢在於通過將命令和查詢的職責分離,為架構師提供了更多的架構屬性選擇,我們可以在查詢側和命令側進行獨立的架構設計。對象級別的職責分離就是CQRS的全部了,但在實踐中涌現出了很多更為靈活也更為複雜的架構風格,比如匯流排的引入、數據模型的分離、一致性報這個策略、事件溯源等等。額外的組件或技術的引入必然導致複雜性和成本上升,這些選型的採納需要團隊的權衡。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • Flutter 3.7 發佈,本人對其中後臺 isolate 通道比較感興趣,迫不及待翻譯了下Aaron Clarke文章,第一次翻譯,有不足地方歡迎各位大佬們評論區指正,我將持續更新到本文,謝謝。 原文地址:Introducing background isolate channels | by ...
  • 這裡給大家分享我在網上總結出來的一些知識,希望對大家有所幫助 這樣封裝列表 hooks,一天可以開發 20 個頁面 前言 在做移動端的需求時,我們經常會開發一些列表頁,這些列表頁大多數有著相似的功能:分頁獲取列表、上拉載入、下拉刷新··· 在 Vue 出來 compositionAPI 之前,我們想 ...
  • 下麵是實現移動端 H5 拍照功能的幾種方法: 1、使用 <input type="file">:通過 HTML5 規範中的 <input type="file"> 調用系統攝像頭,並選擇拍攝的照片。但這種方式可能會導致頁面刷新。 實現移動端 H5 拍照功能的代碼: 在 HTML 中創建一個 <inp ...
  • state 有狀態state的組件稱作複雜組件,沒有狀態的組件稱為簡單組件 狀態里存儲數據,數據的改變驅動頁面的展示 <script type="text/babel"> // 創建組件 class Weather extends React.Component { // 構造器調用1次 const ...
  • 1、模塊化的發展過程 var moduleObj = { userName: 'zhangsan', fn: function () { console.log('hello world') } } 使用方式 <html> <head> </head> <body> <script src="a.j ...
  • 一篇文章帶你瞭解設計模式——創建者模式 在之前的文章中我們已經學習了設計模式的基本原則和基本分類 下麵我們來介紹第一種設計模式,創建型模式的主要關註點是怎樣創建對象,它的主要特點是“將對象的創建與使用分離”。 下麵我們將從下麵四個方面講述五種創建者模式: 單例模式 工廠模式 原型模式 建造者模式 單 ...
  • 後端應用分層是什麼,例如:你用Spring MVC開發web程式、項目用三層架構分包,這些都用了分層思想。 MVC模式包含了三部分: 視圖(view):負責界面顯示、處理用戶交互。如:前端應用 控制器(controller):協調視圖層與模型層之間的相互工作。控制器接收視圖層發來的請求,決定用那些模 ...
  • 設計原則 23種設計模式滿足並實現了設計原則中的一個或者多個,從而達到了代碼復用、增加可維護性的目的。 開閉原則(Open+Closed+Principle,OCP) 里氏代換原則(Liskov+Substitution+Principle,LSP) 依賴倒轉原則(Dependency+Invers ...
一周排行
    -Advertisement-
    Play Games
  • 概述:在C#中,++i和i++都是自增運算符,其中++i先增加值再返回,而i++先返回值再增加。應用場景根據需求選擇,首碼適合先增後用,尾碼適合先用後增。詳細示例提供清晰的代碼演示這兩者的操作時機和實際應用。 在C#中,++i 和 i++ 都是自增運算符,但它們在操作上有細微的差異,主要體現在操作的 ...
  • 上次發佈了:Taurus.MVC 性能壓力測試(ap 壓測 和 linux 下wrk 壓測):.NET Core 版本,今天計劃準備壓測一下 .NET 版本,來測試並記錄一下 Taurus.MVC 框架在 .NET 版本的性能,以便後續持續優化改進。 為了方便對比,本文章的電腦環境和測試思路,儘量和... ...
  • .NET WebAPI作為一種構建RESTful服務的強大工具,為開發者提供了便捷的方式來定義、處理HTTP請求並返迴響應。在設計API介面時,正確地接收和解析客戶端發送的數據至關重要。.NET WebAPI提供了一系列特性,如[FromRoute]、[FromQuery]和[FromBody],用 ...
  • 原因:我之所以想做這個項目,是因為在之前查找關於C#/WPF相關資料時,我發現講解圖像濾鏡的資源非常稀缺。此外,我註意到許多現有的開源庫主要基於CPU進行圖像渲染。這種方式在處理大量圖像時,會導致CPU的渲染負擔過重。因此,我將在下文中介紹如何通過GPU渲染來有效實現圖像的各種濾鏡效果。 生成的效果 ...
  • 引言 上一章我們介紹了在xUnit單元測試中用xUnit.DependencyInject來使用依賴註入,上一章我們的Sample.Repository倉儲層有一個批量註入的介面沒有做單元測試,今天用這個示例來演示一下如何用Bogus創建模擬數據 ,和 EFCore 的種子數據生成 Bogus 的優 ...
  • 一、前言 在自己的項目中,涉及到實時心率曲線的繪製,項目上的曲線繪製,一般很難找到能直接用的第三方庫,而且有些還是定製化的功能,所以還是自己繪製比較方便。很多人一聽到自己畫就害怕,感覺很難,今天就分享一個完整的實時心率數據繪製心率曲線圖的例子;之前的博客也分享給DrawingVisual繪製曲線的方 ...
  • 如果你在自定義的 Main 方法中直接使用 App 類並啟動應用程式,但發現 App.xaml 中定義的資源沒有被正確載入,那麼問題可能在於如何正確配置 App.xaml 與你的 App 類的交互。 確保 App.xaml 文件中的 x:Class 屬性正確指向你的 App 類。這樣,當你創建 Ap ...
  • 一:背景 1. 講故事 上個月有個朋友在微信上找到我,說他們的軟體在客戶那邊隔幾天就要崩潰一次,一直都沒有找到原因,讓我幫忙看下怎麼回事,確實工控類的軟體環境複雜難搞,朋友手上有一個崩潰的dump,剛好丟給我來分析一下。 二:WinDbg分析 1. 程式為什麼會崩潰 windbg 有一個厲害之處在於 ...
  • 前言 .NET生態中有許多依賴註入容器。在大多數情況下,微軟提供的內置容器在易用性和性能方面都非常優秀。外加ASP.NET Core預設使用內置容器,使用很方便。 但是筆者在使用中一直有一個頭疼的問題:服務工廠無法提供請求的服務類型相關的信息。這在一般情況下並沒有影響,但是內置容器支持註冊開放泛型服 ...
  • 一、前言 在項目開發過程中,DataGrid是經常使用到的一個數據展示控制項,而通常表格的最後一列是作為操作列存在,比如會有編輯、刪除等功能按鈕。但WPF的原始DataGrid中,預設只支持固定左側列,這跟大家習慣性操作列放最後不符,今天就來介紹一種簡單的方式實現固定右側列。(這裡的實現方式參考的大佬 ...