CQRS 命令查詢分離架構的多種形式實現
CQRS(命令查詢職責分離)的核心有一個簡單的目標:將讀取和寫入分離爲單獨的模型。這個簡單的想法可以採用多種形式,具體取決於使用它的上下文以及所使用的實現選擇。這篇文章試圖分析 CQRS 的各種形狀,以及所有這些形狀如何支持解耦讀寫的中心思想。
世界變得複雜了。信息系統不再只是簡單的 CRUD 風格的應用程序,並沒有適用於所有類型操作的規範數據模型!
雲、微服務、流媒體、反應式系統意味着現在需要以不同的方式考慮這種多樣化、分佈式和始終動態的數據來源和數據消費。
CQRS 是這種思維過程的結果之一。出於記錄和讀取目的而以不同方式查看數據,並承認這兩種模型不需要相同。這種想法一旦被接受,它就會打開很多選項:
-
· 數據模型
-
· 數據存儲
-
· 一致性選項
-
· 應用設計
-
· 運行時環境
-
· 訪問模式(例如,大量讀取,少量寫入)
請注意,在 CQRS 一詞被創造之前,它的原理通過數據倉庫和分析產品的報告數據庫仍在使用,這在某種程度上遵循了 CQRS 的核心主題,將讀取和查詢解耦。這些產品通常使用某種 ETL 或數據庫複製過程從數據庫中獲取數據,並用作信息系統。雖然這些技術在一個層面上遵循 CQRS,但這個術語現在通常是指單個進程(例如,模塊或微服務)爲不同類型的用戶請求採用讀寫模型。因此,它更像是一種 OLTP 模式,而不是以前的 OLAP 模式。
事實上,擁有分析系統和數據湖等可能是 CQRS 的因果優勢,但這些都不是根本原因。
最後值得一提的一點是,CQRS 在領域驅動設計成爲話題之後開始流行起來。命令和查詢指的是 DDD 中的域事件,因此它不是簡單的 WriteReadResponsibilitySeggretaion,而是隨着這些術語的 DDD 版本而廣爲人知。
下一節嘗試分析可用於實現 CQRS 的不同選項以及這些選項如何影響以下特性:數據模型、一致性、數據庫選擇、傳輸模型。這些選項的順序沒有意義,但可能它們從簡單到相對複雜。
1. 以應用爲中心的 CQRS
CQRS 的簡單實現形式,旨在創建不同的讀寫服務和模型,以在這些操作之間提供清晰的模塊子邊界。命令服務在數據庫中執行寫入。查詢服務從同一個數據庫中讀取數據,但會生成不同的查詢模型,例如用於在 UI 中顯示。查詢模型可以選擇對這些數據進行轉換和投影。
底層數據庫保持不變,因此 DB 約束和模式在兩者中仍然有效。
由於底層數據庫相同,DB 一致性強(*)。
2. 數據庫只讀副本
只讀副本提供不同的讀取數據庫,其中包含主寫入數據庫的只讀副本。
由於數據庫結構與寫入數據庫完全相同,因此服務可以在代碼中使用不同的查詢模型。
此模式用於從查詢中卸載寫入數據庫,以平衡一個或多個讀取實例之間的查詢流量。
複製工具通常由 IBM DB2、Oracle 等 DB 供應商提供。較新的雲原生託管數據庫提供只讀副本作爲託管服務,並負責只讀副本的複製和高可用性。
在特殊情況下,數據庫複製也可以調整爲強一致性(又名複製因子)。在這種情況下,複製是爲了支持強一致性模型(CAP 中的 C)。例如,AWS S3 提供了強大的先寫後讀一致性模型。Cassandra 提供了可配置的讀取一致性級別設置。
3. 事件發佈
許多事件驅動模式補充了 CQRS 風格的應用程序。命令服務寫入命令數據庫。因此,任何改變域狀態的域事件都是一個命令,並被寫入優化了寫入的數據庫。對於先讀後寫的情況,也可以事務性地讀取命令數據庫,但這通常是在聚合的小上下文中。此外,命令服務還將命令發佈到消息傳遞系統和具有規範事件結構的命令事件中。
任何服務都可以訂閱此事件,在這種情況下,查詢服務訂閱會偵聽該事件並構建針對讀取進行優化的本地數據庫。與 DB 複製相反,這種方法提供了以任何讀取優化方式投射事件的自由。事實上,事件底層數據庫的選擇可以與命令數據庫不同,從而實現多語言持久化。
要點:
-
- 查詢端會看到最終一致的數據,因此需要在應用程序設計和用戶體驗中考慮到這一點。對於要用於寫入命令 DB 中的數據,不應讀取查詢 DB。
-
- 命令服務需要以某種方式管理數據庫寫入和事件發佈事務(都可以或都回滾)。這可能意味着 XA 或 2PC 方法可能很脆弱。
-
- 在失敗的情況下,事件可以通過多次重試來傳遞,因此可以多次到達訂閱者(稱爲 “至少一次” 傳遞)。訂閱者需要是冪等的並且只處理一次事件。消息系統增加了 DR 組件,例如持久隊列或死信隊列,以在訂閱者或消息系統出現故障時保留消息。
-
- 根據命令服務的執行時間發佈事件,因此沒有順序保證。這意味着在 T1 發生的域事件 D1 可以晚於在 T2 發出的另一個域事件 D2 到達訂閱者。此外,在訂閱者失敗的情況下,當訂閱者再次健康時,事件可能會亂序重新傳遞。必須解決亂序交付問題(尤其是在 JMS 和 AMQP 風格的消息傳遞中,而在 Kafka 或 Akka & Kinesis Streams 等事件流系統中,消費者可以控制他們爲某個時間消耗的消息偏移量劃分)。通常,這是通過將事件時間戳作爲消息屬性來解決的,該屬性不同於消息中間件在向其發佈事件時創建的時間戳標頭。
4. CDC(變更數據捕獲)
CDC 將命令數據庫作爲命令事件的來源。命令服務只需要事務性地提交到事務數據庫,然後數據庫事務日誌成爲事件觸發器,免除了命令服務來管理數據庫寫入和事務性事件。
這裏引入了額外的日誌抓取組件,它通常是一個數據庫原生組件,並 “掛鉤” 到事件存儲以發佈時間順序事件。
要點:
-
- 參考前面 “事件發佈” #1
-
- 參考前面 “事件發佈” #3。此外,由於命令數據庫事務日誌仍然具有所有更改,因此它提供了另一層以在失敗時重試。
-
- 數據庫事務日誌是有序的,保證了消息的排序。
數據庫供應商爲流行的數據庫提供了一個日誌抓取系統。這些與 CDC 工具結合使用以啓用源和目標連接器。兩個流行的選擇是 Debezium 和 Kafka Connect。Kafka Connect 提供對大量 “源” 和“接收”連接器的支持。
雲數據庫將此模式作爲託管的雲原生服務提供。例如 AWS Dynamo Streams(由 AWS Kinesis Data Stream 支持)。
這種模式也在微服務事務發件箱模式和事務日誌拖尾模式中捕獲。
像 Axon 和 Eventuate 這樣的框架爲這種模式提供了支持。
5. 事件溯源和 CQRS
事件溯源是一種將應用程序設計爲一系列領域事件的方法,這些事件以時間順序存儲在事件存儲中。事件存儲可以是一個事件平臺,例如 Kafka 或 AWS Kinesis,也可以只是一個數據庫。Event Store 是關於狀態的 “單一事實來源”。重要的一點是域沒有 “當前狀態”。當前狀態是派生狀態,可以通過在聚合上重放事件來實現。事件溯源將更改的隱式審計日誌作爲一組域事件提供。另一方面,由於沒有實體狀態,因此無法在命令事件存儲中查詢實體。爲了克服這個問題,CQRS 通常被視爲事件溯源的補充模式,使實體的派生狀態在單獨的查詢數據存儲中可用。
也可以使用 CDC 模式實現 CQRS。如果事件存儲是像 Kafka 這樣的消息傳遞系統,事件存儲將成爲查詢服務(以及任何其他服務,如分析、欺詐預防等)的訂閱,以獲取域命令事件流並創建讀取優化模型用於查詢。
如果命令服務需要讀取一些實體數據怎麼辦?理想情況下,它不應該依賴於先前的狀態,因爲所有命令服務應該做的只是命令的追加日誌(事件存儲的選擇,即消息傳遞與 DB 對這一點來說意義不大)。但是萬一呢?命令服務也可以讀取查詢存儲投影嗎?答案是根據上下文而定的,可能是可以的,但這裏有一個最終的一致性。只要命令處理不依賴於它需要讀取的數據的強一致性保證,它應該可以讀取。可能將其保留爲邊緣情況是謹慎的。
框架:Lightbend Akka & Axon
緩存:讀層緩存是一種通用選擇,在所有選項中都可用,而不是 CQR 的必然結果。
一致性:在這裏,數據一致性考慮是最重要的。只讀數據、命令端讀取和先讀後寫數據的情況需要不同的一致性保證。
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/JgoNbbZxeuezPmqE2Ao6kw