事件溯源模式:分離事件的發生和捕獲兩種不同時間 - verraes
在領域事件中,使用單獨的時間戳來區分事件的發生時間和捕獲時間。
問題
一個領域事件通常有一個時間戳。一個常見的模式是讓eventstore在事件被寫入時新增時間戳。
例如,可以有一個名為record_at的資料庫欄位,其值預設為now()。該欄位被認為是事件後設資料的一部分(而不是特定領域的有效載荷)。該欄位可以在消費該事件的使用者區程式碼中訪問,並可用於基礎設施任務(如按時間順序排列事件)或特定領域的操作、預測、分析...。
這在很多情況下是沒有問題的。對於大多數目的,記錄的時間與事件發生的時間相吻合。許多事件是由系統產生的,或者是使用者產生的行為的直接後果。即使在使用者點選一個按鈕和由此產生的事件被持久化之間有一個小的延遲,其差異也往往可以忽略不計。
畢竟,很多業務流程都是以幾分鐘、幾小時、幾天、甚至幾個月和幾個季度為單位進行操作的。
然而,有時我們實際上關心的是其中的差別。假設每天午夜我們都會收到一份包含當天銀行交易的報表。我們將每筆交易記錄為一個域事件,它的record_at時間戳是在午夜之後。然而,該交易是在前一天的某個地方發生的。如果支付日期對利息的計算、財政利益、法律影響或其他時間敏感方面有影響,這就很重要。
另一個例子是在一個車隊管理系統中跟蹤車禍。當我們持續記錄事件的時候,我們有記錄的時間。但車禍是什麼時候發生的,什麼時候被報告的?
解決方案
識別領域事件型別,在這些型別中,事件發生的時間與記錄的時間是不同的,並且這種差異是重要的。在領域事件的模式中,新增一個反映事件發生時間的領域特定屬性。用它在領域中的用途來命名該屬性。不要有一個預設的now()值,而是依靠事件生產者來填寫這個屬性。
事件的消費者現在可以使用該屬性來做出相關的決定。
案例程式碼
在我們的銀行對賬單示例中,架構可能如下所示:
# (pseudocode, details omitted) - Event - eventId: UUID - recorded_at: timestamp - type: BankAccountWasCredited - payload: - amount: number - currency: string - deposited_at: timestamp |
deposited_at屬性表示實際事務時間,它發生在recorded_at持久時間之前。該事件是多時間的,因為它代表時間的兩個時刻。
為什麼我們不簡單地在事務發生的地方將事件注入到事件庫中的某個地方呢?這將簡化事件本身的設計。
但這其實是個壞主意。
事件儲存庫是按時間順序排列的,所以我們必須插入事件並轉移所有後面的事件。更重要的是,在事件源中,事件是在它們被持久化後釋出的。如果我們在歷史的某個地方注入事件,我們就必須告訴所有的消費者,這個特別的事件是不符合順序的。這給消費者帶來了額外的複雜性。
良好的習慣
即使對於沒有特殊時間戳要求的事件,我也推薦這種技術。這意味著每個事件型別的有效載荷將至少有1個時間戳。
- Event - eventId: UUID - recorded_at: timestamp - type: BankAccountWasCredited - payload: - amount: number - currency: string - deposited_at: timestamp - statement_received_at: timestamp |
請注意,新的 statement_received_at 包含重複的資訊,因為時間戳將等於 recorded_at 的時間戳。
在我看來,與這些好處相比,這只是一個很小的代價。
- 我們現在為該屬性起了一個特定領域的名字,這更好地傳達了設計的意圖。另一個開發者可以理解該屬性在域中的含義,而不必猜測他們是否可以使用record_at欄位來滿足他們的需求。
- 我們有更好的解耦:消費者只需要擔心有效載荷,而不需要依賴後設資料。這是有道理的,因為它將基礎設施的需求與特定領域的需求解耦。由於不依賴後設資料,你可以(在理論上)用不同的供應商替換事件儲存,而不影響領域模型。
- 模型本身的演進也更容易。如果我們依賴於record_at,而後來我們決定需要跟蹤發生,我們就需要調整所有消費者的程式碼。如果我們已經有了特定領域的屬性,我們只需要改變生產者。讓我們看一個例子。
更多點選標題
相關文章
- 事件冒泡 和 事件捕獲事件
- JS事件(事件冒泡和事件捕獲)JS事件
- 理解js的事件冒泡和事件捕獲JS事件
- 事件溯源:是來自事件的狀態與作為狀態的事件? - verraes事件
- 事件溯源中的時間和時間建模 - Tomasz Jaskula事件
- JavaScript事件冒泡、事件捕獲和阻止預設事件JavaScript事件
- 關於js事件冒泡和事件捕獲JS事件
- javascript事件冒泡和事件捕獲型別JavaScript事件型別
- JavaScript 事件捕獲JavaScript事件
- JavaScript事件捕獲冒泡與捕獲JavaScript事件
- javascript的事件監聽與捕獲和冒泡JavaScript事件
- 事件的捕獲、冒泡、委託事件
- JS中的事件順序(事件捕獲與冒泡)JS事件
- WIN32 SDK - 捕獲滑鼠離開和滯留等事件 (轉)Win32事件
- 事件溯源的好處在於可在軟體中捕獲現實世界 – Jessitron事件
- JavaScript高階程式設計筆記 事件冒泡和事件捕獲JavaScript程式設計筆記事件
- Javascript中的事件冒泡與捕獲JavaScript事件
- 事件協作和事件溯源事件
- 事件流與事件溯源事件
- 事件溯源與流水賬的結賬模式事件模式
- 如何捕獲jvm的退出事件啊?JVM事件
- PHP 事件溯源PHP事件
- javascript事件捕獲是什麼意思JavaScript事件
- Javascript事件模型系列(二)事件的捕獲-冒泡機制及事件委託機制JavaScript事件模型
- 當“靈異”事件發生時事件
- WPF捕獲事件即使這個事件被標記為Handled事件
- 拯救祭天的程式設計師——事件溯源模式程式設計師事件模式
- Vue事件獲取觸發事件物件和繫結事件物件Vue事件物件
- 事件溯源:投影或投射模式 -Kacper Gunia事件模式
- 捕獲silverlight滾輪事件事件
- 事件分發和處理事件
- 剖玄析微聚合 - 事件溯源事件
- 事件冒泡、事件捕獲、DOM0級事件處理程式、DOM2級事件處理程式事件
- 事件發生時,你在想什麼?事件
- 事件消費者之 Projector - 事件溯源事件Project
- 事件消費者之 Reactor - 事件溯源事件React
- Java反應式事件溯源之第5部分:事件儲存Java事件
- Spring Boot和EventStoreDB事件溯源案例Spring Boot事件