專注於事件

2006 年 1 月 25 日



這是 進一步的企業應用程式架構開發 的一部分,我在 2000 年代中期撰寫。遺憾的是,自那以後,太多其他事情吸引了我的注意力,所以我沒有時間進一步研究它們,我也看不到在可預見的未來有太多時間。因此,這些材料在很大程度上仍為草稿形式,在我能夠找到時間再次研究它們之前,我不會進行任何更正或更新。

思考企業應用程式的最長久的方式之一是將其視為對外部世界事件做出反應的系統。這是一種思考方式,在 80 年代後半期的結構化設計社群中確立。您現在在「事件驅動架構」的旗幟下聽到了它。

這種思考方式專注於將系統與其世界的互動視為事件的傳輸。透過形成事件清單來理解輸入,該清單描述系統的每個可能輸入,並與外部世界的事件相關聯。同樣地,系統透過向外部系統發出事件信號來宣告其任何重大的變更。

像這樣查看系統事件可以成為系統設計流程的一部分。一個很好的例子是結構化分析後期形式中使用的事件分割技術。它也可以透過明確建立事件物件並將它們輸入某些處理常式中,內建到實作中的系統中。

將我們的注意力集中在這樣的事件上,已經產生許多整合多個系統的興趣 - 這是事件驅動架構的驅動力。透過使用 事件訊息,您可以輕鬆地將發送者和接收者解耦,無論是在身分(您廣播事件,而不必在意誰回應它們)或時間(當接收者準備好處理事件時,可以對事件排隊並轉發)。由於這種鬆散耦合,此類架構提供了極大的可擴充性和可修改性。

然而,在這個部分,我對這些組合並不那麼感興趣 - 儘管它們很有價值。我感興趣的是事件焦點對單一應用程式的影響。當我看到這些時,我注意到許多有趣的特徵,產生了一些非常有趣的機會。其中許多都是相當零碎的,因此我難以在腦海中組織它們。但是,這裡有一些非常有趣的機能的機會,通常不是應用程式中提供的功能類型。

表示事件

到目前為止,我對事件的討論相當鬆散,但當我們深入探討時,我們需要更仔細地檢視事件。基本上,網域事件 傳送出外部世界中發生且應用程式感興趣的事件。它透過某些資料結構傳輸到應用程式,並攜帶描述事件的資料 - 我稱之為來源資料。事件可以來自各種地方 - 訊息系統、使用者介面、資料庫表格上的觸發器。

事件有非常不同的來源資料,因為它們可以代表許多不同的事件。然而,事件應該具有的兩項重要事項是 時間點。我們應該始終考慮兩個不同的 時間點:事件發生的時間和系統注意到事件的時間。(在複雜的系統中,甚至可能有多個注意到時間的時間。)這些 時間點 與我在時間模式中討論的實際和記錄概念密切相關。

除了來源資料外,事件還可能攜帶處理資料,描述我們對事件執行的動作。區分兩者非常重要。特別是,事件的來源資料是不可變的 - 這是我們對所發生事件的了解,我們無法輕易地改變它。(這裡有一個問題,我們可能會取得不正確的來源資料,我稍後會說明。)

由於 網域事件 是物件,因此很容易記錄。通常記錄 網域事件 並保留它們的記錄是有意義的,如果沒有別的,它會產生良好的稽核記錄。

使用事件進行協作

我們習慣將程式區分為多個共同協作的元件。(我故意在此使用模糊的「元件」一詞,因為在此背景下,我的意思是許多事情:包括程式內的物件和透過網路通訊的多個處理程序。)讓它們協作的最常見方式是請求/回應樣式。如果客戶物件想要從業務員物件取得一些資料,它會呼叫業務員物件上的方法來要求它提供該資料。

另一種協作方式為 事件協作。在此方式中,您從未讓一個元件要求另一個元件執行任何動作,而是每個元件在任何變更時發出事件訊號。其他元件會聆聽該事件,並依其希望的方式回應。知名的觀察者模式為 事件協作 的範例。

事件協作 會變更物件執行其責任的許多假設。特別是,它會變更儲存狀態的責任。在要求/回應協作中,我們努力讓只有一個元件儲存特定資料片段,其他元件在需要時會向該元件要求資料。在事件協作中,每個元件會儲存其所需的所有資料,並聆聽該資料的更新事件。在要求/回應協作中,儲存資料的元件通常負責更新資料,在 事件協作 中,負責更新某些資料的元件不需要儲存資料,它所要做的只是確保在更新時觸發事件。

在應用程式中使用事件時,事件協作 並非強制性的,而且 事件協作 與要求/回應之間的選擇並非排他性的選擇。我通常會看到這兩種方式的混合,通常由要求/回應主導。

事件協作 會產生非常鬆散的耦合,這使得在系統中新增新元件變得特別容易,而不需要修改現有的元件。事件協作 的缺點是,很難了解協作。要求/回應協作會在某種形式的程式碼中指定,顯示整個流程,事件協作 則更為含蓄,這使得在發生意外事件時更難除錯。

事件來源

但是,我們可以進一步深入探討稽核記錄,進入一些非常有趣的領域。當系統的所有變更都是由事件造成的時,就會出現這種情況的推手,我稱之為 事件來源。另一種看待此問題的方式是,當我們可以透過處理 網域事件 的記錄來完全推導應用程式的狀態時,就會發生 事件來源

此情況開啟了一些有趣的機會,有些是功能性的,有些是實作性的。立即的實作機會是將應用程式的整個狀態保留在記憶體中,完全不使用持續性資料庫。如果應用程式發生故障,請重新執行事件,可能是從應用程式狀態的定期快照。系統可能會因為效能或簡潔性的原因而選擇這麼做,從系統中移除持續性邏輯可以大幅減少建置和維護系統的精力。當然,並非每個系統都能採用此途徑,但對於某些系統來說,這是一個有用的選項。

由於我們的應用程式狀態完全由我們處理的事件定義,我們可以透過處理 Domain Events 的替代清單來建構替代的應用程式狀態,我稱之為 Parallel Models。特別是,這些允許我們回到過去,建構過去時間的應用程式狀態,以便檢視某人為何執行某項動作,或比較過去與現在的變更。除了調查過去之外,它們還允許我們透過探索替代事件串流來考量尚未發生的事情。如果那場風暴只讓 O'Hare 機場停擺一小時而不是兩小時,星期四會發生什麼事?如果我們將我們的配送週期從每天一次改為兩次,我們的營運會有何不同?

Parallel Models 在企業應用程式中並不常見,但企業應用程式開發人員對它們非常熟悉。原始碼控制系統是任何開發人員工具包中不可或缺的一部分,它們是使用 Event Sourcing 根據需要產生 Parallel Models 的系統。這些 Parallel Models 可能具有歷史性或允許透過分支建立多重實體。因此,原始碼控制系統可以提供一個有價值的比喻來探討 Parallel Models 的好處和問題。

我們可以透過操作事件串流執行的另一件有趣的事情是處理不正確資訊的後果。正如我先前所指出的,事件來源資料是不可變的。我們無法變更我們接收和處理的內容。但是,如果我們接收的內容是錯誤的怎麼辦?如果我們使用 Event Sourcing,我們可以做的是回到事件發生的時間點,並建構一個新的 Parallel Model 來擷取應該發生的事情,本質上是用新的 Retroactive Event 取代不正確的事件。事實上,完全自動化這個程序可能出乎意料地容易,而這個程序通常需要一些複雜且昂貴的手動工作。

因此,Event Sourcing 聽起來很棒,Safeways 有嗎?遺憾的是,有一個陷阱,甚至有幾個陷阱。最簡單的陷阱是程式設計模型,將系統的每個更新取得為可持續事件的形式很尷尬,特別是對於高度互動的系統。這也不自然,所以需要一點時間才能習慣。

事件溯源 的真正難題是連接並非以事件為中心的外部系統。若要重播事件,您需要能夠查詢外部系統的過去狀態。您必須避免向下游系統發送重複的更新。作為 並行模型 典範的原始碼控制系統避免了這一點,因為它們不必進行這種動態整合。但許多企業應用程式與其本身的工作一樣,也與整合有關,因此這些問題顯得非常重要。

這些問題並非無法克服。具體取決於您執行的替代處理方式、與外部系統的整合程度,以及這些外部系統本身使用事件和 事件協作 的程度;它們可能值得解決。即使沒有 事件溯源 的更複雜後果,以這種方式設計系統也能提供出色的稽核功能,同時提供一個基礎,讓您以後可以朝更精密的方面發展。改造 事件溯源 看起來會非常混亂(我這樣說,是因為我還沒見過以這種方式改造自己的系統)。

要考慮的另一件事是,整個系統不必使用 事件溯源。事實上,我大多看過 事件溯源 用於系統的一部分,主要是會計方面。的確,以前嘗試撰寫這些模式時是以會計為基礎,因為那是我看過的地方。會計也有助於緩解 事件溯源 的一些問題,並需要 事件溯源 提供的那種強有力的稽核。

處理事件

無論事件承載系統的所有更新還是僅承載部分更新,它們都提供了一些有趣的替代方案,說明如何組織處理它們的處理邏輯。

一種特別有趣的樣式是使用調度器物件,它會查詢事件的原始資料,以確定哪個實際執行者物件會處理事件。這允許執行者物件保持簡單,而調度器不包含除尋找正確執行者所需的業務邏輯之外的任何業務邏輯。

在概念上,您可以用許多方式來組織這樣的調度器,但我看過一種重複出現的樣式是協議調度器。在此,調度機制的中心組織功能是管理事件內容的合約協議。銷售給客戶的商品可能受持續合約或隱含協議的規範,其中載明與偶爾客戶的常見銷售政策。這種樣式鼓勵一系列委派,其中調度器會詢問事件以找出要將事件傳送至哪個協議,而協議物件會執行進一步的條件檢查,然後鏈會持續進行,直到我們到達最終執行者。

優點在於,可以將執行者與事件配對的大部分邏輯設定在物件間關係中,實際上是資料。這讓系統具有極高的可組態性。特別是,透過將時間包含在這個物件關係網中,我們可以處理事件處理中的另一個常見問題,即因應商業規則的更新。

事件和指令

在這個討論中,我提到透過事件將應用程式狀態的所有變更封裝起來,我可以輕鬆地使用字詞(和模式)「命令」來說明所有這些內容。事件顯然與命令共用大部分的屬性,包括具有自己的生命週期、可以排隊和執行,事件反轉與命令復原相同。

使用事件的一個原因是,這個術語在該領域中廣泛用於這種行為。經常聽到事件驅動架構和事件訊息等術語。

最後我選擇事件,因為我覺得它有一組微妙但重要的關聯。人們認為命令封裝了一個請求 - 使用命令,您可以告訴系統執行 X。然而,事件只傳達某事已發生 - 使用事件,您可以讓系統知道 Y 已發生。另一個不同點是,您會想到向可能感興趣的每個人廣播事件,但只會向特定接收者發送命令。當談到實際行為時,這並不重要。系統對 X 命令的多型反應與 Y 事件沒有什麼不同。但我認為命名確實會影響人們如何看待事件以及他們會建立什麼類型的事件。

命令[四人幫] 中描述的經典模式。您還應該查看 [hohpe-woolf] 以了解 命令訊息事件訊息 之間的對比。


重大修訂

2006 年 1 月 25 日:新增事件協作概述

2005 年 12 月 12 日:第一份草稿上線