事件源模式

【博文目錄>>>】


事件源模式

使用僅追加存儲來記錄描述在域中對數據執行的操作的完整事件序列,而不是僅存儲當前狀態,以便可以使用該存儲來實現域對象。這種模式可以通過避免同步數據模型和業務域的要求,簡化複雜領域中的任務;提高性能、可伸縮性和響應性;爲事務性數據提供一致性;並維護完整的審計追蹤和歷史記錄,以支持採取補償措施。

背景與問題

大多數應用程序使用數據,並且典型的方法是應用程序在用戶使用數據時通過更新數據來維護數據的當前狀態。例如,在傳統的創建,讀取,更新和刪除(CRUD)模型中,典型的數據處理將是從存儲中讀取數據,對其進行一些修改,並使用新值更新數據的當前狀態,通常使用事務鎖定數據。

CRUD方法有一些侷限性:

  • CRUD系統直接針對數據存儲執行更新操作這一事實可能會影響性能和響應能力,並由於所需的處理開銷而限制可伸縮性。
  • 在與多個併發用戶協作的域中,數據更新衝突更有可能發生,因爲更新操作發生在單個數據項上。
  • 除非有額外的審覈機制,將每個操作的細節記錄在單獨的日誌中,否則歷史記錄就會丟失。


要更深入地瞭解CRUD方法的侷限性,請參見MSDN上“CRUD,只有在您能夠負擔的情況下”

解決方案

事件源模式定義了一種處理由一系列事件驅動的數據操作的方法,每個事件都記錄在僅附加的存儲中。應用程序代碼將一系列事件發送到事件存儲區,這些事件必須描述數據上發生的每個操作,在事件存儲中它們被持久化。每個事件表示對數據的一組更改(如AddedItemToOrder)。

事件將持久化在事件存儲區中,該事件存儲充當有關數據當前狀態的真相或記錄系統的源(給定數據元素或信息的權威數據源)。事件存儲通常發佈這些事件,以便通知消費者並在需要時處理它們。例如,消費者可以啓動將事件中的操作應用於其他系統的任務,或者執行完成操作所需的任何其他相關操作。注意,生成事件的應用程序代碼與訂閱事件的系統分離。

事件存儲發佈的事件的典型用途是維護實體的物化視圖,因爲應用程序中的操作會改變它們,並與外部系統集成。例如,系統可能維護用於填充UI部分的所有客戶訂單的物化視圖。當應用程序添加新訂單、添加或刪除訂單上的項以及添加發送信息時,可以處理描述這些更改的事件,並使用這些事件更新物化視圖。


有關更多信息,請參見物化視圖模式

此外,在任何時候,應用程序都有可能讀取事件的歷史,並通過有效地“回放”並消耗與該實體相關的所有事件來實現實體的當前狀態。這可能發生在需要時,以便在處理請求時實現域對象,或者通過計劃的任務來實現,以便將實體的狀態存儲爲支持表示層的物化視圖。

圖1顯示了該模式的邏輯概述,包括一些使用事件流的選項,例如創建物化視圖、將事件與外部應用程序和系統集成,以及重播事件以創建特定實體當前狀態的預測。

在這裏插入圖片描述

圖1-事件源模式的概述和示例

事件源模式提供了許多優點,包括:

  • 事件是不可變的,因此可以使用僅附加的操作來存儲.發起產生事件的操作的用戶界面、工作流或進程可以繼續,處理事件的任務可以在後臺運行。這與事務執行過程中不存在爭用的事實相結合,可以極大地提高應用程序的性能和可伸縮性,特別是在表示級別或用戶界面上。
  • 事件是描述發生的某些操作的簡單對象,以及描述事件表示的操作所需的任何關聯數據。事件不直接更新數據存儲;它們只是在適當的時間被記錄下來以便處理。這些因素可以簡化實施和管理。
  • 事件通常對領域專家有意義,而對象-關係阻抗不匹配的複雜性可能意味着領域專家可能無法清楚地理解數據庫表。表是表示系統當前狀態的人工構造,而不是所發生的事件。
  • 事件來源可以幫助防止併發更新導致衝突,因爲它避免了直接更新數據存儲中的對象的要求。但是,域模型仍然必須設計爲保護自身不受可能導致不一致狀態的請求的影響。
  • 僅附加的事件存儲提供了審計跟蹤,可用於監視針對數據存儲所採取的操作,通過在任何時候重播事件重新生成當前狀態爲物化視圖或投影,並協助測試和調試系統。此外,使用補償事件取消更改的要求提供了反向更改的歷史記錄,如果模型只是存儲當前狀態,則不會發生這種情況。事件列表還可用於分析應用程序性能和檢測用戶行爲趨勢,或獲取其他有用的業務信息。
  • 事件與執行操作以響應事件存儲引發的每個事件的任何任務之間的分離提供了靈活性和可擴展性。例如,處理事件存儲引發的事件的任務只知道事件的性質及其包含的數據。任務的執行方式與觸發事件的操作分離。此外,多個任務可以處理每個事件。這可能使您能夠輕鬆地與只需要偵聽事件存儲引發的新事件的其他服務和系統集成。然而,事件源事件往往級別很低,可能需要生成特定的集成事件。


通過執行響應事件的數據管理任務,以及通過從存儲的事件中物化視圖,事件源通常與CQRS模式相結合。

問題和考慮

在決定如何實現此模式時,請考慮以下幾點:

  • 只有在創建物化視圖或通過重播事件生成數據投影時,系統纔會最終保持一致。在應用程序由於處理請求而將事件添加到事件存儲區、發佈的事件和處理事件的使用者之間存在一些延遲。在此期間,描述對實體的進一步更改的新事件可能已到達事件存儲區。


數據一致性原則有關最終一致性的信息。

  • 事件存儲是不可變的信息源,因此永遠不應該更新事件數據。要撤消更改,更新實體的唯一方法是添加補償事件對於事件存儲,就像您在會計中使用負事務一樣。如果持久化事件的格式(而不是數據)需要更改(可能是在遷移期間),則很難將存儲中的現有事件與新版本結合起來。可能需要迭代所有進行更改的事件,以便它們符合新格式,或者添加使用新格式的新事件。考慮在事件模式的每個版本上使用版本號,以便同時維護舊的和新的事件格式。

  • 多線程應用程序和多個應用程序實例可能在事件存儲中存儲事件。事件存儲中事件的一致性至關重要,影響特定實體的事件順序也是如此(對實體的更改順序影響其當前狀態)。向每個事件添加時間戳是一種可以幫助避免問題的選擇。另一個常見的做法是用增量標識符對請求產生的每個事件進行註釋。如果兩個操作試圖同時爲同一實體添加事件,則事件存儲可以拒絕與現有實體標識符和事件標識符匹配的事件。

  • 沒有標準的方法,也沒有現成的機制,比如SQL查詢,來讀取事件來獲取信息。唯一可以提取的數據是使用事件標識符作爲標準的事件流。事件ID通常映射到各個實體。實體的當前狀態只能通過根據實體的原始狀態重放與其相關的所有事件來確定。

  • 每個事件流的長度會對系統的管理和更新產生影響。如果流很大,請考慮按特定間隔創建快照,例如指定數量的事件。可以從快照中獲取實體的當前狀態,並通過重播在該時間點之後發生的任何事件來獲得該實體的當前狀態。


    有關創建數據快照的更多信息,請參閱Martin Fowler的企業應用程序體系結構網站上的快照和MSDN上的主從快照複製

  • 即使事件源將數據更新衝突的可能性降到最低,應用程序仍必須能夠處理由於最終一致性和事務缺乏而可能出現的不一致。例如,在爲該項目下訂單時,可能會發生表明庫存減少的事件出現在數據存儲中,從而需要協調這兩個操作;可能是通過通知客戶或創建後臺訂單。

  • 事件發佈可能是“至少一次”,因此事件的使用者必須是冪等的。如果事件不止一次被處理,則它們不能重新應用事件中描述的更新。例如,如果一個使用者的多個實例維護某個實體的屬性的聚合(例如所下訂單的總數),則在發生“Order Led”事件時,只有一個實例必須成功地遞增該聚合。雖然這不是事件來源的固有特徵,但這是通常的實現決策。

何時使用此模式

這種模式非常適合以下情況:

  • 當您想在數據中捕獲“意圖”、“目的”或“原因”時。例如,對客戶實體的更改可能被捕獲爲一系列特定的事件類型,例如搬回家, 已結清帳戶,或亡者.
  • 當減少或完全避免發生數據的衝突更新時是至關重要的。
  • 當您想要記錄發生的事件,並且能夠重播它們以恢復系統的狀態時;使用它們回滾對系統的更改;或者簡單地作爲歷史記錄和審計日誌。例如,當一個任務涉及多個步驟時,您可能需要執行操作來恢復更新,然後重放一些步驟以使數據恢復到一致的狀態。
  • 當使用事件是應用程序操作的一個自然特性,並且幾乎不需要額外的開發或實現工作。
  • 當您需要將輸入或更新數據的過程與應用這些操作所需的任務分離時。這可能是爲了提高UI性能,或者將事件分發給其他偵聽器,例如當事件發生時必須採取某些操作的其他應用程序或服務。一個例子是將一個發薪系統與一個費用提交網站相結合,以便該網站和發薪系統使用由事件存儲爲響應在費用提交網站中進行的數據更新而引發的事件。
  • 如果需求發生變化,您希望靈活地更改物化模型和實體數據的格式,或者–當與CQRS一起使用時–您需要調整已讀取的模型或公開數據的視圖。
  • 當與CQRS一起使用時,在更新讀取模型時,最終的一致性是可以接受的,或者,或者,在再水化實體和事件流的數據中所產生的性能影響是可以接受的。

這種模式可能不適用於以下情況:

  • 小或簡單的域,很少或沒有業務邏輯的系統,或與傳統CRUD數據管理機制正常工作的非域系統。
  • 需要對數據視圖進行一致性和實時更新的系統。
  • 不需要審計跟蹤、歷史記錄和回滾和重放操作功能的系統。
  • 對底層數據進行衝突更新的情況非常少的系統。例如,主要是添加而不是更新數據的系統。

示例

會議管理系統需要跟蹤已完成的會議預訂數量,以便在潛在與會者嘗試進行新預訂時檢查是否仍有座位可用。該系統可以至少以兩種方式存儲會議的預訂總數:

  • 系統可以將有關預訂總數的信息作爲一個單獨的實體存儲在一個保存預訂信息的數據庫中。隨着預訂的進行或取消,系統可以酌情增加或減少此數目。這種方法在理論上很簡單,但如果大量與會者試圖在短時間內預訂座位,則可能會導致可伸縮性問題。例如,在最後一天左右的預訂期結束之前。
  • 系統可以將有關預訂和取消的信息存儲爲在事件存儲中舉行的事件。然後,它可以通過重播這些活動來計算可用的座位數。由於事件的不可變性,這種方法可以更具有可伸縮性。系統只需要能夠從事件存儲區讀取數據,或者將數據附加到事件存儲區。有關預訂和取消的事件信息從不修改。

圖2顯示瞭如何使用事件源實現會議管理系統的座位預訂子系統。

在這裏插入圖片描述

圖2-使用事件源捕獲會議管理系統中有關座位預訂的信息

保留兩個席位的行動順序如下:

  1. 用戶界面發出命令爲兩個與會者保留座位。該命令由一個單獨的命令處理程序處理(一段邏輯與用戶界面分離,負責處理作爲命令發佈的請求)。
  2. 一個包含會議所有預訂信息的彙總是通過查詢描述預訂和取消的事件來構建的。這個聚合稱爲可通過性,並且包含在一個域模型中,該模型公開用於查詢和修改聚合中的數據的方法。


需要考慮的一些優化是使用快照(這樣您就不需要查詢和重播完整的事件列表來獲取聚合的當前狀態),並在內存中維護聚合的緩存副本。

  1. 命令處理程序調用域模型公開的方法來進行保留。
  2. 這個可通過性彙總記錄包含保留席位數目的事件。下一次,當總票數適用於各種活動時,所有保留的席位將被用來計算剩餘席位的數量。
  3. 系統將新事件附加到事件存儲中的事件列表中。

如果用戶希望取消座位,系統將遵循類似的處理程序,只是命令處理程序發出生成取消座位事件的命令,並將其附加到事件存儲區。

除了爲可伸縮性提供更多的空間外,使用事件存儲還提供了會議預訂和取消的完整歷史記錄或審計跟蹤。事件存儲中記錄的事件是確定的,也是唯一的真相來源。不需要以任何其他方式持久化聚合,因爲系統可以輕鬆地重放事件並將狀態恢復到任何時間點。


您可以在MSDN上的模式和實踐指南CQRS Journey中的介紹事件源一章中找到有關此示例的更多信息。

相關模式和指導

在實施這一模式時,下列模式和指導也可能相關:

  • 命令和查詢責任隔離(CQRS)模式。爲CQRS實現提供不可變信息源的寫存儲區通常基於事件源模式的實現。命令和查詢責任隔離模式描述瞭如何使用單獨的界面將讀取應用程序中數據的操作與更新數據的操作隔離。
  • 物化視圖模式。基於事件源的系統中使用的數據存儲通常不太適合高效查詢。取而代之的是,一種常見的方法是定期或在數據更改時生成數據的預填充視圖。物化視圖模式顯示瞭如何實現這一點。
  • 補償交易模式。事件源存儲中的現有數據不會更新;而是添加了新條目,這些條目將實體的狀態轉換爲新值。要撤消更改,將使用補償條目,因爲不可能簡單地撤消先前的更改。補償事務模式描述瞭如何撤消上一個操作所執行的工作。
  • 數據一致性入門。將事件源與單獨的讀取存儲區或實例化視圖一起使用時,讀取的數據將不會立即保持一致;相反,它只會最終保持一致。數據一致性入門概述了有關維護分佈式數據一致性的問題。
  • 數據分區指南。使用事件源時,通常會對數據進行分區,以提高可伸縮性,減少爭用並優化性能。數據分區指導指南介紹瞭如何將數據劃分爲離散的分區以及可能出現的問題。

更多信息

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章