# 最前沿 - 適合常見應用程序的事件源
作者?[Dino Esposito](https://msdn.microsoft.com/zh-cn/magazine/mt149362?author=Dino+Esposito)?| September 2015

這是一種自然發生的常見活動,對此您可能甚至不會多加思考。當您想到數據存儲時,您自然會考慮一種可以保存當前數據狀態的格式。盡管有一些大規模系統(如保險或銀行業)可以仔細跟蹤和記錄任何軟件操作,但保存當前數據狀態足以滿足大多數應用程序和網站的需求。
當前狀態存儲做法是獲取系統狀態的快照,并持久保存。數據通常駐留在關系數據庫中。這足以執行新的事務,并檢索過去事務的結果。在過去十年里,當前狀態存儲空間不足的情況并不太常見。
最近以來,業務環境一直在飛速變化。跟蹤業務和域事件變得越來越有必要。事件源 (ES) 是一種影響存儲體系結構以及存儲數據的檢索和插入方式的模式。ES 不僅用于審核和記錄持久域中的業務相關事件,還用于借助較低的抽象級別來保存您的數據,并通過臨時工具和模式來創建多個數據投影。
ES 看似是非常智能炫酷的模式,可以記錄和審核業務功能,但實際上它是一個全新的存儲模型理論,就像是關系模型剛構建時一樣。它對新型軟件的影響甚至可能大于對 NoSQL 存儲的影響。ES 無法替代今天的關系產品和 NoSQL 產品。您可以在關系數據庫和 NoSQL 數據存儲的基礎之上實現 ES。ES 將會改變您對應用程序存儲的看法,并會使用事件(而不是狀態值)作為數據令牌。
## 何時使用事件源會有幫助?
超越當前狀態存儲的一種基本但又常見的方法是跟蹤更新歷史記錄。以一個簡單的書店應用程序為例。每本書都有各自的描述屬性,且您授予用戶編輯權限。當用戶輸入新的描述時,您應該跟蹤舊的描述嗎?
每個人的需求都可能各不相同,但在此示例中,跟蹤更新十分重要。您將如何實現? 一種方法是存儲當前圖書狀態,并在單獨的表中記錄所有更新詳細信息。每次更新都可以有一條對應的記錄。更新記錄會包含更新差異,如每個修改過的列的舊值和新值。
您也可以采用其他方法進行實現。“書”表中可以包含標記了給定 ID 的同一本書的多條記錄。每條記錄均代表按順序列出的已加蓋時間戳的狀態(請參閱圖 1)。
?
圖 1:保留實體歷史記錄的多條記錄
此方案需要使用一個臨時 API 來讀取當前記錄狀態。它并不只是存儲庫中按 ID 選擇記錄的查詢。您必須選取一個具有最新時間戳或最高更新連續編號的記錄。此外,與給定的數據實體相關的所有事件的集合形成了流。事件流是 ES 中的熱門概念。
每當業務要求您跟蹤一系列事件時,ES 都相當有幫助。雖然 ES 看起來可能與日志記錄或審核等橫切關注點類似,但實際上大不一樣。它不會記錄事件來配置處理或跟蹤異常情況。它只會跟蹤業務事件。此外,它也不是橫切關注點,而是一個主要應用于存儲的體系結構決策。
## 定義的事件源
簡而言之,ES 就是將事件用作主要數據源。由于 ES 并不一定適用于所有應用程序,因此開發者樂而忘憂,數十年來都忽略了它。如果您現在認為 ES 似乎毫無用處,那么主要是因為您尚不需要它。
我要總結一下使用 ES 的必要性,如下所述: 如果域專家需要跟蹤軟件可以生成的一系列事件,則事件源是一個可行的選擇。在其他情況下,事件可能仍可用于表達工作流和連接各業務邏輯。不過,在這種情況下,事件并不是域中的高級別成員,可能不會持久保存。這就是今天的主流方案。
接下來,我們要來探討當事件是您應用程序的主數據源時,您需要做些什么。ES 影響存儲的兩個方面:持久性和查詢。持久性以 3 項核心操作為特征:插入、更新和刪除。在 ES 方案中,插入操作仍幾乎與保存當前實體狀態的典型系統中的插入操作相同。系統接收請求,并將新事件寫入存儲。事件中包含唯一標識符(例如 GUID)、類型名稱或標識事件類型的代碼、時間戳和相關信息。
更新操作由數據實體的同一容器中的另一個插入操作組成。新條目僅反映以下數據相關信息:哪些屬性已改變、新值是什么、是否與業務域相關、為什么要更改以及是如何更改的。在執行更新操作后,數據存儲會不斷演進,如圖 2?所示。
?
圖 2:指明更新 ID 為 #1 的實體的新記錄
刪除操作與更新操作的工作方式幾乎相同,區別僅在于,刪除操作包含不同的信息,明確指明這是一項刪除操作。
在查詢方面,如此進行更新會立刻導致一些問題出現。您如何知道是否存在給定記錄,或者當前記錄狀態可能是什么? 這就需要使用一個臨時查詢層,從概念上選擇具有匹配 ID 的所有記錄,然后逐個事件分析數據集。
例如,它可以根據已創建事件的內容新建一個數據實體。然后,它會重播所有連續的步驟,并返回流結束時保留的信息。我們將這項技術稱為事件重播。通過簡單地重播事件來重新生成狀態可能會引發一些性能擔憂。
以銀行賬戶為例。某客戶多年前開立了一個銀行賬戶,從那時起就積累了數百個操作和事件。若要獲取當前余額,您必須重播幾百個操作,才能重建當前賬戶狀態。這并不總是可行。
對于此情景,解決辦法是有的。最重要的是創建快照。快照是在給定的時間保存實體的已知狀態的記錄。這樣一來,就無需重播快照日期之前的事件了。
ES 并不與任何技術或產品綁定,無論是特定的關系數據庫,還是 NoSQL 數據存儲。ES 確實提高了人們對至少一個特殊軟件組件(即事件存儲)的需求性。從本質上來講,事件存儲就是事件日志。您可以使用您自己的代碼,在您選擇的任意數據存儲 API 的基礎之上創建一個。
事件存儲具有兩大主要特征。首先,它是一種僅限追加的數據存儲。它不支持更新操作,可能會僅有選擇性地支持特定類型的刪除操作。其次,事件存儲必須能夠返回與給定密鑰相關的事件流。您可以自行創建這一層代碼,也可以使用可用的工具和框架。
## 事件存儲選項
您可以使用任何有效的選項來實現事件存儲。它通常使用關系數據庫或某個版本的 NoSQL 數據存儲作為持久引擎。如果您計劃使用關系數據庫,則可以每種實體類型一個表(每個事件各占一行)。
事件通常有不同的布局。例如,每個事件可能有不同數量的屬性要保存,這樣一來,您就很難為所有行制定出一個公用的架構。如果可以根據所有可能列的集合生成一個公用架構,且此架構的運行效果是可接受的,那么這就是一個可以輕松實現的選項。
在其他情況下,您可以考慮 SQL Server 2014 的列索引存儲功能,此功能可將表配置為在垂直列(而不是水平行)中存儲數據。適用于所有版本的 SQL Server 的另一個選項是,將事件屬性規范化為一個 JSON 對象,并將它作為字符串存儲在一列中。
在 NoSQL 行話中,“文檔”是指屬性數量可變的對象。一些 NoSQL 產品專用于存儲文檔。從開發者的角度來看,這再簡單不過了。只需創建一個類,在其中填充值,然后按原樣進行存儲即可。類的類型是關聯多個事件的關鍵信息。如果您使用 NoSQL,那么您就有一個事件對象,只需保存它即可。
## 正在執行的項目
ES 是相對較新的體系結構方法。有助于在基于事件的數據存儲的基礎之上編寫代碼的標準工具仍在不斷涌現。雖然您肯定可以自行安排 ES 解決方案,但也可以使用一些臨時工具,它們能夠幫助您以更結構化的方式處理事件存儲。
使用事件感知數據存儲的主要優勢在于,數據庫等工具可保證您只執行讀取和追加事件的操作,以確保事件源方法的業務一致性。一個專為存儲事件而設計的框架是 NEventStore 項目 ([neventstore.org](http://neventstore.org/))。此項目可方便您寫入和讀取回發事件,并不受持久性制約進行操作。保存事件的方法如下:
~~~
var store = Wireup.Init()
? .UsingSqlPersistence("connection")
? .InitializeStorageEngine()
? .UsingJsonSerialization()
? .Build();
var stream = store.CreateStream(aggregateId);
stream.Add(new EventMessage { Body = eventToSave });
stream.CommitChanges(aggregateId);
~~~
若要讀取回發事件,請通過已提交的事件的集合打開流并循環。
事件存儲 ([geteventstore.com](http://geteventstore.com/)) 的工作原理也是為事件流提供適用于普通 HTTP 和 .NET 的 API。在 ES 行話中,“聚合”等同于存儲中的流。您可以對事件流執行 3 項基本操作:寫入事件;讀取上一個事件、特定事件和多個事件中的一個;以及訂閱以獲得更新。
訂閱分為以下 3 種類型。第一種是易失訂閱,即將事件寫入給定的流會調用回叫函數(每次都被調用)。第二種是彌補訂閱,即對于存儲中的每個事件(從給定事件開始)以及之后添加的任何新事件,您都會收到通知。最后一種是持久訂閱,適用于多個使用者正在等待處理事件的情形。訂閱可保證事件至少傳遞給使用者一次,但實際可能按照無法預知的順序傳遞多次。
## 總結
事件源使用事件作為應用程序數據源。您無需構建應用程序來保存實體的上次已知狀態,但需要構建相關業務事件的列表。事件數據源在較低的抽象級別存儲數據。您需要從那里將投影應用到事務和查詢所需的實際實體狀態。投影是指重播事件和執行一些任務的過程。最明顯的投影是構建當前狀態;但您可以擁有任何數量或類型的非事件投影。
* * *
Dino Esposito?*是《Microsoft .NET: Architecting Applications for the Enterprise》(Microsoft Press,2014 年)和《Programming ASP.NET MVC 5》(Microsoft Press,2014 年)的合著者。作為 JetBrains 的 Microsoft .NET Framework 和 Android 平臺的技術推廣人員,Esposito 經常在全球行業活動中發表演講,并在?[software2cents.wordpress.com](http://software2cents.wordpress.com/)?上以及?[twitter.com/despos](http://twitter.com/despos)?上的推文中分享他對于軟件的愿景。*
- 介紹
- 云連接移動應用 - 借助身份驗證和離線支持構建 Xamarin 應用
- 崛起 - 自由 Internet 廣播
- Microsoft Azure - 云中的容錯問題和解決方法
- 最前沿 - 適合常見應用程序的事件源
- Azure 深入了解 - 跨云平臺創建統一的 Heroku 式工作流
- 借助 C++ 進行 Windows 開發 - Windows 運行時中的高級類型
- 編譯器優化 - 借助按本機配置優化來簡化代碼
- 數據點 - 再探 JavaScript 數據綁定(現在包含 Aurelia)
- 云安全 - 借助 Azure 密鑰保管庫保護敏感信息的安全
- 測試運行 - 借助人工尖峰神經元進行計算
- 開發運營 - 在 Microsoft 堆棧上啟用開發運營
- 孜孜不倦的程序員 - 如何成為 MEAN: Node.js
- 新型應用 - 提升新型應用的易用性的做法
- 別讓我打開話匣子 - Darwin 的照相機
- 編輯寄語 - 汽車 Internet 發生故障