事件協作和事件溯源

banq發表於2022-06-17

雖然事件確實無處不在,但受歡迎程度的增長似乎導致術語準確性的喪失。Apache Kafka 成為“事件匯流排”,所有非同步訊息都被宣告為事件,使用事件流被宣告為 Event Sourcing,像這樣模糊不同的概念對架構討論沒有幫助。

術語Event Sourcing、Event Streaming和Event Collaboration值得仔細研究和區分。在解決這些問題之前,讓我們先勾勒一下我們所處的環境。這篇文章是關於實現業務流程的應用程式,由各種子系統或服務組成。


事件定義
一個事件是一個事實。它描述了過去已經發生的事情。

這個簡單的洞察力已經對軟體架構產生了深刻的影響。比如說。對一個事件沒有任何爭論。當我收到一個事件時,沒有必要去驗證它,我不能懷疑它。它所描述的事情已經發生了。如果一個事件的接收者不喜歡它,或者不能處理它,這總是接收者的問題,而不是傳送者的問題。

(在實踐中,我見過這樣的系統,事件的釋出者試圖跟蹤接收者方面的成功處理,甚至可以在出現錯誤的情況下自己採取進一步的行動。這背後可能有良好的意圖,但它是基於對事件語義的根本誤解。使用事件驅動通訊的一個重要動機就是要避免這種複雜性!)

以上對事件術語的定義是非常籠統的。在(微)服務之間的通訊背景下,它可能會導致誤解,因為它還包括UI事件(MouseClicked, ButtonPressed)、日誌事件或物聯網資料。在業務系統架構的背景下,我們應該更具體一點,將自己限制在與我們的業務領域相關的 "有趣 "的事件。讓我們把這些稱為 "領域事件"。

領域事件定義
領域事件描述了過去發生的一個有趣的事實,並在領域內產生影響。

領域事件完全沒有失去它的事實特徵,上述更一般的事件定義仍然適用。

事件流
領域事件起源於哪裡?在一個服務中,業務物件的狀態發生變化。該服務在內部堅持這種狀態變化。如果它認為該狀態變化與它的內部環境無關,它將通知感興趣的外部世界該變化已經發生。為此,一個事件被髮布在訊息匯流排上。為此建立的技術被稱為 "基於日誌的訊息匯流排",最著名的代表是Apache Kafka。在執行過程中,這導致了一個連續的事件流,其他服務可以訂閱。因此,基於釋出和訂閱這些流的通訊的架構模式被稱為 "事件流"。

定義:事件流
將事件釋出到其他服務可以訂閱的通道上。

就像事件這個詞本身一樣,這是一個非常廣泛的術語,事件流也被用於上述的日誌和物聯網資料的例子。對於商業應用的各個服務透過事件流相互通訊這種更具體的情況,術語Event Collaboration已經確立(例如,見Ben Stopford的書《Designing Event-Driven Systems》,O'Reilly 2018)。

定義:事件協作
多個元件使用領域事件的Event Streaming進行通訊。

當一個服務訂閱了來自另一個服務的事件流時,它可以使用這個來生成傳送者業務物件的內部表示。讓我們來看看我們的簡單例子中的PaymentService。它訂閱了OrderCreated事件,並可以在內部儲存訂單的有趣部分。這樣,它就可以記住每筆付款中哪些貨物是要用它來支付的。在查詢的情況下,PaymentService可以提供關於付款的額外資訊,而不必在執行時依賴OrderService。

只要資料所產生的事件在匯流排上仍然可用,就可以丟棄本地表示,並從事件中重新建立。對於已經接觸過事件源的讀者來說,這聽起來會很熟悉。但是,雖然這與它有一些共同點(從一系列的事件中獲取資料),但我們不應該僅僅將其稱為事件源。我們需要區分兩個不同的視角:一個服務的內部和外部世界。讓我們退一步,看看事件源的基本原理。

事件溯源
不是儲存當前狀態,而是儲存導致該狀態的事件序列。

事件源通常以 "命令-查詢責任隔離 "的形式與額外的檢視相結合,簡稱CQRS。在這裡,讀取和寫入資料的資料模型被分開。在CQRS與事件源的結合中,事件日誌作為寫的一方。寫入方的每次更新,都會有一個或多個投影被轉換成其他格式。例如,這可以是一個關係模型,然後允許我們輕鬆地檢索在所有付款中積累的資料--一個典型的查詢模型。另一個投影也可以是為外部世界的這次更新生成一個事件,並在匯流排上釋出。外部世界的事件不一定要和內部的事件完全一樣

將內部事件投射到出去的事件流中是事件源和事件協作之間的橋樑。一個服務的內部狀態變化會產生一個外界感興趣的領域事件。它透過事件流釋出給感興趣的消費者。

附錄1:事件的型別
事件的不同用途(一方面用於內部資料管理的事件源,另一方面用於基於事件流的服務間的事件協作)使Martin Fowler等人對不同型別的事件進行了分類。在他關於這個主題的文章中,他談到了三類:事件源事件、通知事件和事件攜帶的狀態轉移。

事件源事件在這裡應該佔據一個特殊的位置。如上所述,這些與事件協作無關,而是與服務內部的持久化有關。事件源事件是允許物件的狀態從一系列的事件中被完全重新建立的事件。

在協作領域,Fowler區分了事件通知(其本身不包含任何資料,除了對變化物件的引用)和事件攜帶的狀態轉移。

事件協作中的標準情況應該是事件攜帶的狀態轉移。在這裡,與事件相關的資料是在事件中提供的。在支付方式改變的情況下,為了留在支付區域,這將是,例如,被指定為新支付方式的銀行細節。

另一方面,一個通知事件,只說有東西被改變了。為了獲得相關資料,必須從釋出事件的服務中查詢受影響物件的當前狀態。這至少有兩個缺點。

首先,如果傳送的參考是指一個易變的物件,事件和資料之間的聯絡就會丟失。可以想象,當事件在接收器處被處理時,另一個變化已經發生了。也許一個訂單被改變了,但隨後不久就取消了。後來,當通知事件被接收並查詢狀態時,結果將是一個意外的狀態(甚至是一個錯誤資訊)。

此外,查詢的必要性破壞了事件驅動架構的一個主要優勢。我們希望透過減少執行時的依賴性來提高我們系統的可靠性。在執行時,PaymentService不應該依賴OrderService來回答任何請求。

通知事件應該只在選定的情況下使用。例如,如果事件的有效載荷太大,以至於在匯流排上釋出它在技術上沒有意義。通知事件中的引用應該總是指代一個不可變的物件。一個有用的用例是檔案的生成。一旦文件的生成完成,就會釋出一個帶有靜態檔案的URL的通知。

回到事件攜帶的狀態轉移--這裡是開發者或架構師的工作,以有意義的方式確定哪些資料應該被包含。只有實際改變的資料?還是被改變的完整物件?或者任何介於兩者之間的東西?在Mathias Verraes的這篇帖子中可以找到關於這個問題的有趣想法。


banq注:領域事件其實是一個業務概念,不能將其歸於”事件“這個大的概念下,容易與系統的各種事件混淆,事件風暴和事件溯源是針對領域事件,或者領域活動,在這裡將domain event中的event翻譯成”活動“可能更好,領域中發生的活動,非常直接簡單,也區別與各種技術上EDA或資料分析中的事件概念,也與運維監控中的系統事件區分開來,事件活動是DDD最主要關心的事件。
 

相關文章