CQRS之旅——旅程8(後記:經驗教訓)

旅程8:後記:經驗教訓

咱們的地圖有多好?咱們走了多遠?咱們學到了什麼?咱們迷路了嗎?

「這片土地可能對那些願意冒險的人有益。」亨利.哈德遜
複製代碼

這一章總結了咱們旅程中的發現。它強調了咱們在這個過程當中所學到的最重要的經驗教訓,提出了若是咱們用新知識開始這段旅程,咱們將以不一樣的方式作的一些事情,並指出了Contoso會議管理系統的一些將來道路。git

你應該記住,這個總結反映的是咱們的具體旅程,並不是全部這些發現都適用於你本身的CQRS旅行。例如,咱們的目標之一是探索如何在部署到Microsoft Azure並在利用雲的可伸縮性和可靠性的應用程序中實現CQRS模式。對於咱們的項目,這意味着使用消息傳遞來支持多個角色類型和實例之間的通訊。您的項目可能不須要多個角色實例,或者沒有部署到雲中,所以可能不須要如此普遍地(或者根本不須要)使用消息傳遞。github

咱們但願這些發現可以被證實是有用的,特別是當您剛剛開始使用CQRS和事件源時。web

咱們學到了什麼

本節描述了咱們學到的主要經驗教訓。它們沒有以任何特定的順序呈現。數據庫

性能問題

在咱們的旅程開始時,咱們對CQRS模式的一個概念是,經過分離應用程序的讀和寫方面,咱們能夠優化每一個方面的性能。CQRS社區的許多人都認同這一觀點,例如:windows

「CQRS告訴我,我能夠分別優化讀和寫,並且我沒必要老是手動的反規範化到平面表中。」瀏覽器

  • Kelly Sommers - CQRS顧問

這在咱們的實踐過程當中獲得了證明,當咱們確實須要解決性能問題時,這種分離使咱們受益不淺。性能優化

在旅程的最後階段,測試揭示了應用程序中的一組性能問題。當咱們研究它們時,發現它們與咱們實現CQRS模式的方式關係不大,而與咱們使用基礎設施的方式關係更大。發現這些問題的根源是困難的,因爲應用程序中有如此多的活動部件,得到正確的跟蹤和用於分析的正確數據是一項挑戰。一旦咱們肯定了瓶頸,修復它們就相對容易了,這主要是由於CQRS模式使您可以清楚地分離系統的不一樣元素,好比讀和寫。儘管實現CQRS模式所致使的關注點分離會使識別問題變得更加困難,可是一旦您識別出一個問題,不只更容易修復它,並且更容易防止它的重現。解耦的體系結構使得編寫重現問題的單元測試更加簡單。架構

咱們在處理系統中的性能問題時遇到的挑戰更多地是因爲咱們的系統是一個分佈式的、基於消息的系統,而不是由於它實現了CQRS模式。異步

第7章,「添加彈性和優化性能」提供了關於咱們處理系統中性能問題的方法的更多信息,並對咱們想要進行但沒有時間實現的額外更改提出了一些建議。分佈式

實現消息驅動系統遠非易事

咱們這個項目的基礎設施是在旅程中根據須要開發它。咱們沒有預料(也沒有預先警告)須要多少時間和精力來建立應用程序所需的健壯基礎設施。咱們在許多開發任務上花費的時間至少是最初計劃的兩倍,由於咱們持續發現與基礎設施相關的額外需求。特別是,咱們從一開始就瞭解到擁有健壯的事件存儲是相當重要的。咱們從經驗中獲得的另外一個關鍵思想是,消息總線上的全部I/O都應該是異步的。

Jana(軟件架構師)發言:
雖然咱們的事件存儲還不是生產環境完備的,可是若是您決定實現本身的事件存儲,那麼當前的實現很好地指示了應該處理的問題類型。

儘管咱們的應用程序並不大,但它向咱們清楚地說明了end-to-end跟蹤的重要性,以及幫助咱們理解系統中全部消息流的工具的價值。第4章「擴展和加強訂單和註冊限界上下文」描述了測試在幫助咱們理解系統方面的價值,並討論了由咱們的顧問之一Josh Elster建立的消息傳遞中間語言(messaging intermediate language, MIL)。

Gary(CQRS專家)發言:
若是咱們有一個用於消息傳遞的標準符號,就能夠幫助咱們與領域專家和核心團隊以外的人員溝通一些問題,這也會有所幫助。

總之,咱們一路上遇到的許多問題都與CQRS模式沒有特定的關係,而是與咱們解決方案的分佈式、消息驅動特性更相關。

Jana(軟件架構師)發言:
咱們發現,使用不一樣的Topic來傳輸由不一樣聚合發佈的事件,經過這樣來劃分服務總線有助於實現可伸縮性。有關更多信息,請參見第7章「 添加彈性和優化性能」。另外,請參閱這些博客文章:「 Microsoft Azure Storage Abstractions and their Scalability Targets」和「 Best Practices for Performance Improvements Using Service Bus Brokered Messaging」。

使用雲帶來的挑戰

雖然雲提供了不少好處,好比可靠的、可伸縮的、現成的服務,您只需單擊幾下鼠標就可使用這些服務,可是雲環境也帶來了一些挑戰:

  • 您可能沒法在任何您想要的地方使用事務,由於雲的分佈式特性使得ACID(原子性、一致性、隔離性、持久性)事務在許多場景中不切實際。所以,您須要瞭解如何使用最終的一致性。例如,請參見第5章「準備發佈V1版本」,以及第7章「添加彈性和優化性能」中減小UI延遲的部分章節。
  • 您可能須要從新檢查關於如何將應用程序組織到不一樣層的假設。例如,參見第7章「添加彈性和優化性能」中關於進程內同步命令的討論。
  • 您不只必須考慮瀏覽器或內部環境與雲之間的延遲,還必須考慮在雲中運行的系統的不一樣部分之間的延遲。
  • 您必須考慮到瞬時錯誤,並瞭解不一樣的雲服務可能如何實現節流。若是您的應用程序使用幾個可能被節流的雲服務,那麼您必須協調應用程序如何處理不一樣服務在不一樣時間進行節流。

Markus(軟件開發人員)發言:
咱們發現,代碼中只有一個總線抽象,這掩蓋了這樣一個事實,即有些消息是在本地進程內處理的,有些消息是在不一樣的角色實例中處理的。要查看這是如何實現的,請查看 ICommandBus接口以及 CommandBusSynchronousCommandBusDecorator類。第七章「 增長彈性和優化性能」包括了對 SynchronousCommandBusDecorator類的討論。

備註:咱們的Visual Studio解決方案中的多個構建配置是爲部分解決這個問題而設計的,也幫助人們下載和使用代碼來快速入門。
複製代碼

CQRS是不一樣的

在咱們的旅程開始時,有人警告咱們,儘管CQRS模式看起來很簡單,但實際上它要求您在考慮項目的許多方面時進行重大的轉變。咱們在旅途中的經歷再次證實了這一點。您必須準備拋棄許多假設和預先設想的想法,在開始充分理解從模式中得到的好處以前,您可能須要先在幾個限界上下文中實現CQRS模式。

這方面的一個例子是最終一致性的概念。若是您來自關係數據庫背景,而且已經習慣了事務的ACID屬性,那麼在系統的全部級別上接受最終的一致性並理解其含義是一個很大的步驟。第5章「準備發佈V1版本」和第7章「添加彈性和優化性能」都討論了系統不一樣領域的最終一致性。

除了與您可能熟悉的不一樣以外,尚未一種正確的方法來實現CQRS模式。因爲咱們對模式和方法的不熟悉,咱們在功能塊上作了更多錯誤的開始,而且對所需的時間估計不好。隨着咱們對這種方法愈來愈熟悉,咱們但願可以更快地肯定如何在特定狀況下實現模式,並提升咱們估算的準確性。

Markus(軟件開發人員)發言:
CQRS模式在概念上很簡單,而細節才決定成敗。

咱們花了一些時間來理解CQRS方法及其含義的另外一種狀況是在限界上下文之間的集成期間。第5章「準備發佈V1版本」詳細討論了團隊如何處理會議管理與訂單和註冊上下文之間的集成問題。這部分旅程揭示了一些額外的複雜性,當您使用事件做爲集成機制時,這些複雜性與限界上下文之間的耦合級別有關。咱們的假設是,事件應該只包含關於聚合或限界上下文中變化的信息,但事實證實這種假設是沒有幫助的,事件能夠包含對一個或多個訂閱者有用的附加信息,並有助於減小訂閱者必須執行的工做量。

CQRS模式爲如何劃分系統引入了額外的思考。您不只須要考慮如何將系統劃分爲層,還須要考慮如何將系統劃分爲限界上下文,其中一些上下文將包含CQRS模式的實現。在旅程的最後階段,咱們修改了關於層的一些假設,將一些處理從最初完成處理的工做者角色引入到web角色中。在第7章「增長彈性和優化性能」中討論瞭如何在進程中發送和處理命令。應該根據領域模型將系統劃分爲限界上下文,每一個限界上下文都有本身的領域模型和通用語言。一旦肯定了限界上下文,就能夠肯定在哪些限界上下文中實現CQRS模式。這將影響如何以及在何處須要實現這些隔離限界上下文之間的集成。第二章「[分解領域]」介紹了咱們對Contoso會議管理系統的所做的決策。

Gary(CQRS專家)發言:
單個進程(部署中的角色實例)能夠承載多個限界上下文。在此場景中,您不須要爲限界上下文使用服務總線來彼此通訊。

實現CQRS模式比實現傳統的(建立、讀取、更新、刪除)CRUD風格的系統更復雜。對於這個項目,第一次學習CQRS和建立分佈式、異步消息傳遞基礎設施的開銷也很大。咱們在此過程當中的經驗清楚地向咱們證明了爲何CQRS模式不是頂級體系結構。您必須確保實現基於CQRS的限界上下文相關的成本是值得的,一般,您將在高競爭、高協做的領域中看到CQRS模式的好處。

Gary(CQRS專家)發言:
分析業務需求、構建有用的模型、維護模型、用代碼表示它以及使用CQRS模式實現它都須要時間和金錢。若是這是您第一次實現CQRS模式,那麼您還須要對基礎設施元素(如消息總線和事件存儲)進行開銷投資。

事件源和事務日誌

對於事件源和事務日誌是否等同於同一件事,咱們進行了一些討論:它們都建立了所發生事情的記錄,而且都容許您經過重播歷史數據來從新建立系統的狀態。結論是,事件的顯著特徵是除了記錄所發生的事實以外,還能捕獲意圖。有關咱們所說的意圖的更多細節,請參閱參考指南中的第4章「深刻CQRS和ES」。

涉及到領域專家的

實現CQRS模式鼓勵領域專家的參與。該模式使您可以將寫端上的領域和讀端上的報告需求分離出來,並將它們與基礎設施關注點分離開來。這種分離使領域專家更容易參與系統中他的專業知識最有價值的方面。使用領域驅動的設計概念,如限界上下文和通用語言,也有助於集中團隊的注意力,並促進與領域專家的清晰溝通。

咱們的驗收測試證實是一種有效的方法,可讓領域專家參與進來並獲取他的知識。第4章「擴展和加強訂單和註冊有界上下文」詳細描述了這種測試方法。

Jana(軟件架構師)發言:
做爲一個反作用,這些驗收測試還有助於咱們處理僞生產版本的快速發佈,由於它們使咱們可以在UI級別運行一組完整的測試,以驗證除單元測試和集成測試以外的系統行爲。

除了幫助團隊定義系統的功能需求以外,領域專家還應該參與評估一致性、可用性、持久性和成本之間的權衡。例如,領域專家應該幫助肯定何時手動流程是可接受的,以及在系統的不一樣區域中須要什麼級別的一致性。

Gary(CQRS專家)發言:
開發人員傾向於將全部內容都鎖定到事務中,以確保徹底的一致性,但有時並不值得這樣作。

什麼時候使用CQRS

如今咱們已經完成了咱們的旅程,咱們如今能夠建議您應該評估的一些標準,以肯定是否應該考慮在應用程序中的一個或多個限界上下文中實現CQRS模式。您能正面回答的問題越多,就越有可能將CQRS模式應用到給定的限界上下文中,從而使您的解決方案受益:

  • 限界上下文是否實現了業務功能的一個領域,這個領域是您的市場中的一個關鍵區別點?
  • 限界上下文本質上是否與可能在運行時具備高爭用級別的元素協做?換句話說,多個用戶是否會爲了訪問相同的資源而競爭?
  • 限界上下文是否可能經歷不斷變化的業務規則?
  • 您是否已經具有了健壯的、可伸縮的消息傳遞和持久性基礎設施?
  • 可伸縮性是這個限界上下文面臨的挑戰之一嗎?
  • 限界上下文中的業務邏輯複雜嗎?
  • 您清楚CQRS模式將給這個限界上下文帶來的好處嗎?

Gary(CQRS專家)發言:
這些都是經驗法則,不是硬性規定。

若是咱們從新開始,會有什麼不一樣?

本節是咱們反思咱們的旅程的結果,以及肯定了一些咱們想以不一樣方式去作的事情和一些咱們但願追求的其餘機會。若是在咱們掌握瞭如今咱們所瞭解的CQRS和ES知識以後重來一次的話。

從消息傳遞和持久性的堅實基礎設施開始

咱們將從一個可靠的消息傳遞和持久性基礎設施開始。咱們採起的方法是從簡單的先開始,並根據須要創建基礎設施,這意味着咱們在旅程中積累了技術債務。咱們還發現,採用這種方法意味着在某些狀況下,咱們對基礎設施的選擇影響了咱們實現領域的方式。

Jana(軟件架構師)發言:
從旅行的角度來看,若是咱們從一個堅實的基礎設施開始,咱們將有時間處理領域中一些更復雜的部分,好比等待列表(Wating-list)。

從一個可靠的基礎設施開始也能使咱們更早地開始性能測試。咱們還將進一步研究其餘人如何在基於CQRS的系統上進行性能測試,並在其餘系統上尋找性能基準,好比Jonathan Oliver的EventStore

咱們採起這種方法的緣由之一是咱們從顧問那裏獲得的建議:「不要擔憂基礎設施。」

更多地利用基礎設施的能力

從一個堅實的基礎設施開始也將容許咱們更多地利用基礎設施的能力。例如,當咱們發佈一個事件時,咱們使用消息發起者的ID做爲會話ID在Azure服務總線傳遞,但從系統處理事件的部分來看,這並不老是最好的使用會話ID的方式。

做爲其中的一部分,咱們還將研究基礎設施如何支持其餘最終一致性的特殊狀況,如時間一致性、單調一致性、「read my writes」和自我一致性。

咱們想探討的另外一個想法是使用基礎設施來支持版本之間的遷移。咱們能夠考慮使用基於消息的流程或實時通訊流程來協調把新版本上線,而不是針對每一個版本以特定的方式處理遷移。

採用更系統的方法來實現過程管理器

咱們在旅程的早期就開始實現咱們的過程管理器,而且仍然在強化它,並確保它的行爲在旅程的最後階段是冪等的。一樣,從爲流程管理人員提供一些堅實的基礎設施支持開始,使他們更有彈性,這將對咱們有所幫助。可是,若是咱們要從新開始,咱們也會等到過程的後期再實現流程管理器,而不是直接開始。

在旅程的第一階段,咱們開始實現RegistrationProcessManager類。第3章「訂單和註冊限界上下文」描述了初始實現。在旅程的每一個後續階段,咱們都對流程管理器進行了更改。

以不一樣的方式劃分應用程序

在項目開始時,咱們會更仔細地考慮系統的分層。咱們發現咱們的劃分的方式是把應用程序分到web角色和工做者角色中,這在第4章「擴展和加強訂單和註冊限界上下文「中進行了描述。但這不是最優的,在旅程的最後階段,在第7章「增長彈性和優化性能」中,做爲性能優化的一部分,咱們對架構作了一些重大改變。

例如,在旅程的最後階段,做爲從新組織的一部分,咱們在web應用程序中引入了同步命令處理,同時引入了已存在的異步命令處理。

以不一樣的方式組織開發團隊

咱們學習CQRS模式的方法是迭代開發、回顧、討論,而後重構。可是,咱們能夠經過讓幾個開發人員在相同的特性上獨立工做,而後比較結果,從而學到更多。這可能揭示了更普遍的解決方案和方法。

評估領域域和限界上下文是否適合使用CQRS模式

咱們但願從一組更清晰的啓發開始(如本章前面概述的啓發),以肯定特定的限界上下文是否會受益於CQRS模式。若是咱們關注領域中更復雜的地方,好比等待列表(Wating-list),而不是訂單、註冊和支付的限界上下文,咱們可能會學到更多。

性能計劃

咱們將在旅程的早期處理性能問題。咱們尤爲要:

  • 提早設定明確的性能目標。
  • 在過程當中更早地運行性能測試。
  • 使用更大更實際的負載。

咱們沒有作任何性能測試,直到旅程的最後階段。有關咱們發現的問題以及如何解決這些問題的詳細討論,請參見第7章「添加彈性和優化性能」。

在旅程的最後階段,咱們在服務總線上引入了一些分區,以提升事件的吞吐量。此分區是基於事件的發佈者完成的,所以由同一個聚合類型發佈的事件將發佈到同一個Topic。咱們但願把當前使用一個Topic的擴展到使用多個Topic,可能會基於消息中OrderID的hash進行分區(這種方法一般稱爲分片)。這將爲應用程序提供更大的擴展。

以不一樣的方式思考UI

咱們認爲UI與讀寫模型交互的方式,以及它處理最終一致性的方式都很好,而且知足了業務需求。特別是,UI檢查預訂是否可能成功並相應地修改其行爲的方式,以及UI容許用戶在等待更新讀模型時繼續輸入數據的方式。有關當前解決方案如何工做的更多細節,請參見第7章「添加彈性和優化性能」中的「優化UI」一節。

咱們想研究除非絕對須要,其餘避免在UI中等待的方法,好比使用瀏覽器推送技術。在某些地方,當前系統中的UI仍然須要等待針對讀模型的異步更新。

探索事件源的一些額外好處

咱們發如今旅程的第三階段,第5章「準備發佈V1版本」中,修改訂單和註冊限界上下文來使用事件有助於簡化這個限界上下文的實現,一部分是由於它已經使用了大量的事件。

在當前的旅程中,咱們沒有機會進一步探索靈活性的承諾,以及從事件源中挖掘過去事件以得到新的業務看法的能力。可是,咱們確實確保系統保存了全部事件的副本(不只僅是那些重建聚合狀態所需的副本)和命令,以便在未來啓用這些類型的場景。

Gary(CQRS專家)發言:
一樣有趣的是,經過事件源或其餘技術(如數據庫事務日誌或SQL Server的StreamInsight特性)來挖掘過去的事件流以獲取新的業務洞察是否更容易實現?

探索關於限界上下文集成的相關問題

在咱們的V3版本中,全部限界上下文都由同一個核心開發團隊實現。咱們但願研究在實踐中,由不一樣開發團隊實現的限界上下文與現有系統集成起來有多容易。

這是您爲學習經驗作出貢獻的一個很好的機會:繼續實現另外一個限界上下文(請參閱產品backlog中的優秀用戶故事),將它集成到Contoso會議管理系統中,並在旅程的另外一章中描述您的經驗。

相關文章
相關標籤/搜索