詳解CQRS架構模式

{"type":"doc","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"從一開始,軟件系統就被用於各種用途,針對它們的需求也隨着時間的推移而增長。需求的變更可能與業務邏輯、伸縮性或系統的其他方面有關。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"爲了滿足這些相互矛盾或重疊的需求,工程師必須在設計系統時做出各種各樣的權衡。問題在於,很多權衡在一開始並不是必需的,而當需要做出權衡時,系統已經演變成到無法做出權衡的地步。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在我看來,最有害的設計鎖定通常發生在數據層。在設計典型的應用程序數據模型時,通常會結合考慮領域知識與性能因素。領域知識規定了實體是什麼以及它們在邏輯上如何相互關聯,性能因素決定了它們是如何在物理層面實現的(例如:採用關係型數據庫還是NoSQL數據庫、主鍵、索引等)。這兩個方面的選型讓應用程序能有效地爲目標場景提供服務。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https:\/\/static001.geekbang.org\/infoq\/30\/30203fe9c6a4867ed48215a90ad525c2.jpeg","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":"center","origin":null},"content":[{"type":"text","text":"數據及其不同的視圖"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在擁有大量數據和複雜實體模型的大型應用程序中,一些實現細節隨着時間推移變成了“核心”部分。有時候,這些東西是工程師在很明確的情況下完成的,但更多的是以一種隱式甚至是無意的方式發生。於是,新需求可能與現有的實現不一致,以至於根本無法很好地容納它們。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"這類問題在不同的情況下需要不同的解決方案。在本文中,我將重點關注一種情況,即從應用程序讀取數據的方式與向系統寫入數據的方式非常不同時所出現的問題。這裏的不同點可以是指查詢模式、輸出格式或規模方面的不同。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"我在"},{"type":"link","attrs":{"href":"https:\/\/kislayverma.com\/programming\/asynchronous-programming-a-cautionary-tale\/?fileGuid=0IWvR8dLbi0m7fi4","title":"","type":null},"content":[{"type":"text","text":"這篇文章"}]},{"type":"text","text":"裏寫了自己所遇到的這種情況。我當時正在開發的訂單管理系統使用了實體ID (訂單ID、商品ID等),但是隨着時間推移,出現了一些複雜的讀取需求,我們的數據模型無法支持這些需求。問題出在兩個方面:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"一方面,現有的實現很難有效地滿足新的查詢模式。另一方面,訂單數據的讀取方希望有一種截然不同的數據模型。例如,電子商務平臺上的賣家希望他們的大客戶數據切片能以特定的方式來呈現,而面向客戶的應用程序希望數據看起來與購物車中的樣子一樣。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"這種情況並不少見,特別是對於擁有核心實體的系統。它們封裝的數據被廣泛使用,因此需要提供多種不同的格式。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"那麼,我們該如何彌合這一鴻溝?"}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"CQRS"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"CQRS是“命令查詢責任分離”(Command Query Responsibility Segregation)的縮寫。在基於CQRS的系統中,命令(寫操作)和查詢(讀操作)所使用的數據模型是有區別的。命令模型用於有效地執行寫\/更新操作,而查詢模型用於有效地支持各種讀模式。通過領域事件或其他各種機制將命令模型中的變更傳播到查詢模型中,讓兩個模型之間的數據保持同步。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https:\/\/static001.geekbang.org\/infoq\/70\/70d7a63860003065415f615404f5cffb.jpeg","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如果你覺得它們看起來就像是兩個不同的微服務,那麼我來說一說它們之間的一個細微區別。從物理實現層面來看,這兩個數據模型可以作爲兩個獨立的微服務,甚至可以用一個命令模型來支持多個查詢模型。但是,微服務架構的一個關鍵構造是兩個微服務通常代表兩個獨立的領域,而在CQRS中,無論運行時架構是怎樣的,命令模型和查詢模型都屬於同一邏輯領域。如果查詢模型對命令模型一無所知,就無法發揮作用。這裏的耦合是預期的,不同於微服務之間的解耦行爲。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"CQRS並沒有規定這兩個模型如何保持同步。同步可以通過同時更新兩個模型來同步實現,也可以通過消息代理(如Kafka)將命令從命令模型傳輸到查詢模型來異步實現。後一種比較常用,因爲它讓系統更加可伸縮,儘管它需要在寫操作和讀操作的最終一致性方面做出權衡。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https:\/\/static001.geekbang.org\/infoq\/45\/45e77f7336d8007f57afe4b4c36c2794.jpeg","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"這不就是緩存嗎?"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"只用於讀取的數據模式看起來就像是一個緩存。事實上,查詢模型可以使用Redis這樣的緩存技術來實現。但是,CQRS不只是爲了分離數據的寫入和讀取,它的根本目的是爲了實現數據的多重表示,每一種表示都能夠滿足某些用戶的需求。CQRS可能會有多種查詢模式,每個模式可能使用不同的物理實現。有些可能使用數據庫,有些可能使用Redis,等等。"}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"什麼時候應該使用CQRS"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"對於一部分場景,CQRS是一種非常有用的架構模式。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"第一個是我在前面已經提到過的。如果同一個數據模型不能有效地滿足系統的讀和寫模式,那麼通過應用CQRS來解耦讀寫是很有意義的。解耦後的數據模型可以滿足特定的需求。CQRS有效地將單個數據表示變成任意數量的(讀)表示,所有這些表示都與負責處理所有更新的核心表示保持一致。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"適用CQRS的第二個場景是將讀負載與寫負載分開。前面我講了緩存和CQRS的區別,緩存並不是應用CQRS的目的。但是,通過分離命令模式和查詢模式,就有了對單個模式進行伸縮的可能性。查詢模型可以有自己的數據庫和緩存,可以使用最適合某些特定場景的技術來實現。但不管怎樣,命令模型的伸縮都不會受制於查詢模型。我在這裏需要重申的是,它們不是獨立的系統,儘管它們之間有深度的耦合,但這不是問題。"}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"什麼時候不該使用CQRS"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在系統中使用CQRS會帶來顯著的認知負擔和複雜性。開發人員必須面對至少兩個數據模型和多種技術選擇,所有這些都是不可忽略的負擔。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"第二個問題是如何保持命令模型和查詢模型的數據同步。如果選擇了異步方式,那麼整個系統就要承擔最終一致性所帶來的後果。這可能非常麻煩,特別是當用戶希望系統能夠立即反映出他們的操作時,即使是單個一致性要求也會危及整個系統的設計。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如果我們選擇讓模型在任何時候都保持一致,就會有CAP和兩階段提交問題。如果兩個模型使用同一個支持ACID的數據庫,我們可以通過事務來保持它們的一致性,但CQRS的很多可伸縮性優勢就發揮不出來了。如果要支持多個查詢模型,寫操作將會越來越慢,因爲需要更新所有的查詢模型。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"因爲這兩個問題的存在,在選擇是否使用CQRS時就要十分謹慎。如果使用得當,它可以極大提升應用程序的伸縮性。但是,支持多個數據模型並不是件容易的事,所以應該只在沒有其他方法可以滿足要求時才考慮這麼做。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"原文鏈接:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"https:\/\/kislayverma.com\/software-architecture\/architecture-pattern-cqrs\/?fileGuid=0IWvR8dLbi0m7fi4"}]}]}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章