Event-Souring模式

Event-Sourcing模式使用僅附加存儲來記錄或描述域中數據所採取的動作,從而記錄完整的一系列系列事件,而不是僅存儲實體的當前狀態。因爲存儲包含全部的事件,可以用來具體化域對象。Event-Sourcing模式可以簡化複雜的域中的任務,避免了數據模型和業務領域的同步和引發的爭用問題;增強性能,擴展性,以及響應;爲事物數據提供一致性;保留全部的事件執行歷史,可以跟蹤和實現回滾之類的補償操作。

問題

大多數的應用都會涉及到數據的處理,而通常的方法是由應用來保證數據的狀態,當用戶請求數據的時候,就會更新數據的狀態。舉個例子,在傳統的CRUD模型中,典型的數據處理過程是,從數據倉庫讀取數據,做出一些修改,使用新的數據更新(通常通過事務來保護數據)當前數據的狀態。CRUD的方式會有如下的一些限制:

  • 事實上,CRUD系統直接針對數據存儲執行更新操作,可能會受到數據倉庫的處理開銷而影響系統的性能和響應時間,和限制的可擴展性。
  • 在許多併發用戶的協作域中,數據更新有很大的可能發生衝突,因爲更新操作都是發生在單個數據項上。
  • 除非有一個額外的檢查跟蹤機制,它記錄在一個單獨的日誌中的每個操作的詳細信息,否則,數據項的操作歷史會丟失。

想要更深入的瞭解CRUD架構的一些限制,可以參考微軟的CRUD, Only When You Can Afford It一文。

解決方案

Event-Sourcing模式定義了一種處理由一系列事件來驅動的數據操作的方法,每一事件都記錄在一個僅能追加的存儲中。應用將生成一系列事件來描述每一個動作,每個事件對應存儲數據發生的變更,然後將這些事件持久化。每個事件都表示一組數據的變化(比如在訂單中增加數據項)。

事件可以持久化到事件存儲倉庫中。事件存儲倉庫作爲對當前數據的狀態的數據源或記錄系統(數據源或信息的權威數據源)。事件存儲通常會發布這些事件,以便可以通知消費者,並在消費者有需要的時候處理它們。例如,消費者可以初始化一些任務將事件中的操作應用於其他系統來執行,或執行完成該操作所需的任何其他相關操作。注意,生成事件的應用程序代碼與訂閱事件的系統是解耦的。

通常來說,事件存儲所發佈的事件是用來在應用程序對實體進行修改的時候,更新實體的Materialized視圖,並與外部系統集成。例如,系統可以維護所有客戶訂單的Materialized視圖,這些訂單信息用於填充UI的部分。當應用程序添加新的訂單,添加或刪除項目的順序,並添加航運信息,消費者將處理描述這些變化的事件,並更Materialized視圖。

參考Materialized-View模式來了解更多的詳細信息。

此外,事件存儲可以讓應用在任何時候讀取事件的歷史,並通過對事件歷史進行有效地“回放”和消耗有關該實體的所有事件來計算實體的當前狀態。很多時候,可能會因爲客戶端請求,或通過一個預定的定時任務,來重新計算實體的當前狀態。其中,定時任務可以將計算的狀態生成Materialized視圖,並存儲起來來提高查詢效率。

圖1展示了Event-Sourcing模式邏輯的概覽圖。其中也包括了使用事件流來創建一個Materialized視圖,並與其他外部應用和系統集成,以及通過重複事件流來展示某些實體的當前狀態。

這裏寫圖片描述

圖1. 關於Event-Sourcing模式的例子和概覽

Event-Sourcing模式有很多的優點,包括:

  • 事件都是不可變對象,並且存儲在一個僅支持追加的隊列中。任何用戶界面,workflow,或者程序觸發了產生事件的行動,都可以以非阻塞任務繼續執行,並且這些任務可以都在後臺持續執行。這一點,可以令結合事務執行過程中不會產生任何爭用,可以極大地提高應用程序的性能和可擴展性,特別是對於用戶界面的展示。
  • 事件是描述事件發生的簡單對象,以及描述事件所代表的動作所需的相關數據。事件不會直接更新數據存儲區,它們只是在適當的時候進行記錄的生產者。這些方面都可以簡化系統的實現和管理。
  • 事件通常在某些領域都有各自的特殊含義,相對而言,對象可能只是和數據庫的表相對應的,是無法表示其在領域中的真實意義的。表只能表示系統的當前狀態,而表現不了發生的事件。
  • Event-Sourcing可以有效減少併發更新所引起的衝突,因爲Event-Sourcing避免了直接更新數據存儲區中對象狀態的要求。當然,域模型仍然需要設計來保護其自身的一致性。
  • 僅追加的時間存儲可以提供Trace的線索,用來監測對數據存儲區所採取的行動,並在任何時間計算當前實體狀態的Materialized視圖或執行事件的回放,協助測試和系統調試。此外,使用補償事件取消更改的要求提供了一個逆轉狀態的歷史記錄,如果模型只存儲當前狀態,補償回滾是無法實現的。事件列表也可用於分析應用程序性能和檢測用戶的行爲趨勢,以獲得其他有用的業務信息。
  • Event-Sourcing的事件存儲解耦了其生產者(產生事件的任務)和消費者(生成Materialized視圖的任務等),爲系統的提供了更好靈活性和可擴展性。例如,處理事件存儲所發佈的事件只知道事件的性質和包含的數據。執行任務的方式與觸發事件的操作是解耦的。此外,可以使用多個任務可以處理事件。這一特點使得事件倉庫可以很容易的配合其他服務和系統進行集成,只需要偵聽由事件存儲引發的新事件即可。然而,Event-Sourcing往往是非常低的level,如果有必要的話,可以對基礎事件進行簡單的聚合再來配合其他系統進行集成。

Event-Sourcing通常都是配合CQRS模式來執行數據管理任務的。

實現Event-Sourcing模式的問題和顧慮

當在考慮實現Event-Sourcing模式的時候,需要考慮如下一些問題:

  • 整個系統在創建Materialized視圖或通過回放事件數據生成預測的時候都是不一致的,只能滿足最終一致性的。應用程序將事件添加到事件存儲區的過程與處理請求的結果、正在發佈的事件以及處理它們的事件的消費者之間存在一些延遲。在此期間,描述實體的新的更新的新事件可能纔到達事件存儲區。

    可以參考Data Consistency Primer來了解最終一致性方面的信息

  • 事件存儲是不可變的信息源,因此事件數據不應該被更新的。更新實體以撤消更改的唯一方法是向事件存儲添加補償事件,就像在交易系統中使用負事務一樣。如果持久化事件的格式(而不是數據)需要更改,比如在遷移過程中,將存儲中的現有事件與新版本事件相結合很難。可能需要遍歷所有事件的變化使事件符合新的格式。考慮在事件結構中定義版本,並對每個版本上使用版本標記,以維護新舊事件的不同格式。
  • 多線程應用程序和多個應用程序實例都可能在事件存儲區中存儲事件。事件存儲中事件的一致性是至關重要的,因爲這將影響特定實體的事件順序(實體發生變化的順序影響其當前狀態)。在每一個事件中都添加一個時間戳,可以幫助避免問題。另一個常見的做法是每一個產生的事件都標記一個增量標識符。如果兩個操作試圖同時爲同一實體添加事件時,則事件存儲可以拒絕與現有實體標識符和事件標識符相匹配的事件。
  • 在遍歷事件來獲取信息這方面,是沒有標準的方法或者一些諸如SQL查詢的內建機制的。數據的提取只能通過標識符,獲取事件流的方式來。事件ID通常映射到單個實體。一個實體的當前狀態,只能通過回放所有涉及對該單位的原始狀態的事件來計算出來。
  • 事件流的長度也會影響管理和更新系統。如果事件流很大,計算負載高,可以考慮每隔一定的時間間隔創建快照,例如指定的事件數。這樣,實體的當前狀態可以從快照和重放那個時間點之後發生的任何事件中獲得。

想了解針對數據建立快照方面更多的信息,可以參考Martin Fowler的Enterprise Application Architecture website以及Master-Subordinate Snapshot Replication on MSDN.
  • 即使事件獲取將數據衝突更新的可能性降到最低,應用程序仍然必須能夠處理可能通過最終一致性和事務缺乏而出現的不一致。例如,表示庫存減少的事件可能到達數據存儲區,而該項目的還在進行下單,從而導致協調兩個操作的出現衝突,就需要應用能夠處理這種類似的衝突;可能需要通知客戶撤單或創建回訂單。
  • 事件發佈可能發生多次,所以事件的消費操作必須是冪等的。事件的消費者必須能夠處理重複的通知操作。例如,如果一個實體的某一個屬性由多個應用實例維護,比如訂單總數,那麼,當下訂單的時候,必須只有一個成功增加訂單總數的事件的發生。雖然這不是Event-Sourcing的固有特性,但是通常來說,實現Event-Sourcing都要考慮這一點。

何時使用Event-Sourcing模式

Event-Sourcing模式很適合以下場景:

  • 當開發者希望捕獲數據中的“意圖”、“目的”或“原因”時,很適合使用Event-Sourcing。例如,對客戶實體的更改可以記錄爲一系列特定的事件,如搬家、關閉賬戶等。
  • 當儘量減少更新數據的發生爭用的時候,使用Event-Sourcing是很合適的。
  • 當開發者希望記錄發生的事件,並能夠重放它們以恢復系統的狀態;使用事件來回滾系統變更;或簡單地將事件作爲歷史和審計日誌等情況下,均適合使用Event-Sourcing模式。例如,當任務涉及多個步驟時,您可能需要執行回滾某個更新的操作,然後重新執行一些步驟,使數據重新恢復到一致狀態。
  • 當使用事件是應用程序運行的一個自然特性,並且不需要太多的額外開發或實現工作的時候,使用Event-Sourcing模式很合適。
  • 當開發者需要將輸入或更新數據的過程與應用這些操作所需的任務解耦時,適合使用Event-Sourcing模式。這種方式也可以用來改善UI性能,或者將事件分發給其他監聽器,如其他應用程序或服務,來進行集成。
  • 當開發者想能夠根據需求靈活改變Materialized模型和實體數據的格式的情況下,當開發者需要在使用CQRS模式的時候,適配讀模型或者視圖的時候。
  • 當配合CQRS模式共同使用時,接受最終一致性的延遲,以及允許根據事件重新計算讀模型或者Materialized視圖的狀態的情況下,也可以使用Event-Sourcing模式

Event-Sourcing模式在如下的情況下不太適用:

  • 領域模型很小,或者業務較爲簡單,系統只有很少或沒有業務邏輯,或非域系統,與CRUD架構就能很好滿足需求的情況下,不適合使用Event-Sourcing。
  • 系統對一致性和實時更新的數據視圖要求較高的時候,不適合使用Event-Sourcing模式。
  • 系統不需要審計跟蹤、歷史和回滾和重放操作的功能的時候,因爲複雜性的原因,不適合使用Event-Sourcing模式。
  • 數據更新衝突很少的情況下,並不適用於使用Event-Sourcing。例如,系統的主要工作是添加數據而不是更新數據的情況。

使用舉例

某個會議管理系統需要跟蹤完成預訂的會議,該系統可以在某個希望參加會議的人嘗試申請參加的時候,檢查是否還有空閒座位。該系統可以以至少兩種方式來存儲預定庫存的總數:

  • 該系統可以將預訂庫存總量的信息作爲一個單獨的實體在存儲在數據庫中。可以預訂或取消,系統可以適當的增加或減少這個數字。這種方法在理論上是很簡單,但是如果大量的預定的人在很多的時間內試圖預訂的座位的話,可能導致擴展性問題。例如,在預訂期結束之前的最後一天內,可能有大量的預定請求。
  • 該系統可以將預訂和取消的事件存儲到數據倉庫中。庫存總量可以通過遍歷這些事件的來計算。由於事件的不變性,這種方法的可擴展性更好。系統只需要能夠從事件存儲區讀取數據,或將數據添加到事件存儲區。關於預訂和取消事件的信息不會修改的。

圖2顯示瞭如何使用Event-Sourcing來實現會議管理系統的座席預訂子系統。

這裏寫圖片描述

圖2在會議管理系統中使用Event-Sourcing獲取座位預訂信息

保留兩個座位的動作順序如下:
1. 用戶界面發出一個命令,爲兩位與會者預留座位。該命令由一個單獨的命令處理程序處理(一個與用戶界面解耦的應用邏輯,負責處理作爲命令發送的請求)。
2. 通過請求所有的預定和取消會議的事件,來獲取一個包含全部預定信息的數據集合。這個數據集合叫SeatAvailability,並支持查詢其中的庫存信息。

可以使用快照來做一些優化(因此開發者不需要查詢和重放事件的完整列表以獲取的SeatAvailability的當前狀態),並在內存中維護聚合的緩存副本。
3. CommandHandler調用由域模型的方法來預定或取消。
4. SeatAvailability記錄包含座位庫存。下一次計算庫存數時,將遍歷所有的預訂時間來計算還有多少庫存。
5. 該系統增加的新事件會存儲在事件倉庫中。

如果用戶希望取消座位,該系統遵循類似的過程,由命令處理程序產生一個取消座位的事件並將其添加到事件存儲中。除了提供了更好的擴展性外,事件倉庫爲所有的預定和取消操作都保留了操作歷史,無論是爲了跟蹤,或者分析挖掘,都提供了良好的支持。因爲事件倉庫中記錄的事件是真實的唯一來源。系統沒有必要持久化其他的狀態信息,因爲可以很容易地重新遍歷事件並計算任何時間點實體狀態。

開發者可以在Introducing Event Sourcing中更詳細的瞭解這個例子。

相關的其他模式

當考慮實現Event-Sourcing模式的時候,也可以參考如下相關模式:

  • CQRS模式.CQRS實現中所提供的不可變信息源的寫存儲通常就是基於Event-Sourcing模式的一種實現。CQRS模式描述瞭如何將操作,讀取數據和應用程序的操作,通過使用單獨的更新數據的接口來分離職能。
  • Materialized-View模式.在Event-Sourcing模式中所使用的數據倉庫,通常來說是不利於查詢的。通常提高查詢效率的方式,就是每隔一定的時間,根據數據倉庫生成預填充的視圖來提高查詢效率。Materialized-View模式描述了改功能是如何實現的。
  • Compensating-Transaction模式. 在實現Event-Sourcing模式中的數據倉庫中的數據是不會執行更新操作的,通常,都會通過增加額外的事件來執行回滾等操作,來恢復實體的狀態。Compensating-Transaction模式描述如何撤消由前一個操作執行的工作。
  • Data Consistency Primer. 當使用Event-Sourcing時,讀數據倉庫或者Materialized視圖是不會保證實時一致的。相對來說,他們會保證最終一致性。Data Consistency Primer中講述了分佈式數據保證一致性的諸多問題。
  • Data Partitioning Guidance. 在使用Event-Sourcing的時候,爲了提升擴展性,優化性能,減少爭用,會考慮對事件存儲進行分區。Data Partitioning Guidance中描述瞭如何將數據進行分區,以及分區中可能產生的問題等。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章