什麼是事件溯源Event Sourcing?
什麼是Event Sourcing?
“傳統”儲存應用程式變化資料的方式是儲存當前狀態。例如,您的應用程式可能是一個日曆,所以您想儲存約會。它可能看起來像這樣:
約會ID 開始時間 結束時間 標題 1 09:30 10:45 會議 2 11:15 11:30 飯局 <p class="indent"> |
這是一張看起來很熟悉的資料表。它可能是關聯式資料庫中的一個表,或一個鍵值儲存中的一組文件,甚至一個儲存在記憶體中的物件列表。重點是,它代表了系統的狀態,因為它是現在。讓我問你一個問題:這個系統是如何進入這個狀態的?
審計日誌audit log
要回答這個問題,您可以建立一個審計日誌。除了記錄建立或更新約會,在審計日誌中您還將記錄描述所發生的事件(一個“事件”)。對於一個單一的約會,它可能看起來像這樣:
Sequence 事件Event 1 約會被建立: 時間:09:30 - 10:30 Title: 開會 2 約會重新被安排: 09:30 - 10:45 3 標題被改變: 飯局 <p class="indent"> |
相比如果你只有系統的當前狀態,這揭示了一個大量的資訊。在約會被建立一次,約會改期一次,它的標題被改變了。10:30是預約結束時間,但它後來被改期,之前的預約資訊會消失。
這種資訊對應用程式的使用者來說是非常有用的,因為他們可以看到發生了什麼事,他們應該責怪誰。開發人員也將從中獲利最多;當你看到為什麼導致某個特定的狀態時,尋找問題的原因就變得容易得多了。
單一真理之源
除了你的當前狀態以外存在一個審計日誌是有用的,但有一個問題:衝突。如果你發現自己在目前的狀態A,而你的審計日誌說是B,你現在有兩個問題。你不僅要診斷手頭的問題,而且你也必須找出其中的差異原因。
問題是,你有兩個“真相”的來源,陳述你係統的狀態應該是什麼樣的。您的應用程式只能檢視到當前狀態,所以它沒有任何問題。你作為一個開發人員面對兩個來源的真相。因為他們衝突,也就都沒有真正值得信任,如同一個人戴兩隻表,如果時間不一樣,都無法信任了。
事件溯源
如果我們消除審計日誌,我們只有一個真相的來源,但我們失去了所有詳細的歷史資訊,這些資訊我們非常重視。如果我們消除了當前的狀態呢?
這是事件溯源的本質:它不是儲存系統的當前狀態,您只儲存導致該狀態的事件。為了獲得當前狀態,您可以在記憶體中“重播”這些事件。
當前狀態只是發生了事件的“短暫”表示。它不是恆久不變的。你可以改變你的應用程式的狀態,但你不能改變事件。他們已經成為不爭的事實。
重播
為了得到您的系統的當前狀態,您將必須重播所有的事件。所有的事件?嗯,不是真的都是。您當前的狀態通常被分為幾個邏輯的“物件”,這將是傳統上一個表中的行。如果您唯一標識每個物件,您只需要重播該物件的事件,以獲得該物件的狀態。
讓我們來看看一個非常簡單的例子,我們的日曆目前的狀態。
class Appointment { public Guid AppointmentId { get; } public DateTime StartTime { get; private set; } public DateTime EndTime { get; private set; } public string Title { get; private set; } public bool IsCanceled { get; private set; } } <p class="indent"> |
事件看上去如下:
class AppointmentCreated { public Guid AppointmentId { get; } public DateTimeOffset StartTime { get; } public DateTimeOffset EndTime { get; } public string Title { get; } } class AppointmentRescheduled { public DateTimeOffset StartTime { get; } public DateTimeOffset EndTime { get; } } class AppointmentRenamed { public string Title { get; } } class AppointmentCanceled { } <p class="indent"> |
AppointmentCanceled 事件沒有任何屬性。它的型別代表發生了一個有意義的事件。
從約會視角重播這些事件看起來像什麼呢?
void ReplayEvent(AppointmentCreated @event) { AppointmentId = @event.AppointmentId; StartTime = @event.StartTime; EndTime = @event.EndTime; Title = @event.Title; } void ReplayEvent(AppointmentRescheduled @event) { StartTime = @event.StartTime; EndTime = @event.EndTime; } void ReplayEvent(AppointmentRenamed @event) { Title = @event.Title; } void ReplayEvent(AppointmentCanceled @event) { IsCanceled = true; } <p class="indent"> |
你開始一個空白物件。然後為每個發生的事件簡單呼叫ReplayEvent,按照它們發生的順序。這是所有您需要重新建立當前狀態的步驟。
修改狀態
因為狀態是代表過去發生的事件,修改狀態就是透過新增事件實現。獲得當前狀態是物件的一種職責責任,它方便讓所有的業務邏輯放在一個地方,包括業務規則的知識,它是DDD中的哲學。
當前狀態的物件(或實體物件或領域物件,不管你怎麼稱呼它)也會對自己負責一致性;它是唯一的創造事件的角色,也負責從過去的事件重播決定是否允許或禁止某個操作:你的約會已經取消;你就不能再取消了。
class Appointment { public Appointment( Guid id, DateTimeOffset startTime, DateTimeOffset endTime, string title) { if(endTime < startTime) throw new EndTimeBeforeStartTimeException(); AppendEvent( new AppointmentCreated(id, startTime, endTime, title)); } public void Reschedule( DateTimeOffset startTime, DateTimeOffset endTime) { AppendEvent( new AppointmentRescheduled(startTime, endTime)); } public void Cancel() { if (IsCanceled) throw new AppointmentAlreadyCanceledException(); AppendEvent( new AppointmentCanceled()); } } <p class="indent"> |
AppendEvent 是一個方法,該方法將為指定的唯一物件也就是約會Appointment代表的物件新增一個事件到儲存中。
在方法內完成引數的驗證。它使得一種方法最終負責資料的有效性和一致性。為簡潔起見,Reschedule 不驗證引數,但Cancel根據目前的狀態檢查操作是否允許。
當前,如果您試圖取消已被取消的約會,將引發異常。通常不會這樣。我們可以跳過檢查,並只是儲存事件。它不會改變已被取消的事實;當重播時,我們還將設定iscanceled屬性為true時。這裡的要點是:你不必總是需要維護和基於狀態驗證。有時它會忽略多餘的事件。
注意,所有的構造器和Reschedule或Cancel 的方法都是儲存事件。他們並沒有直接修改狀態。為什麼?我們已經有了一個基於事件修改狀態的方法:ReplayEvent方法。所以,除了儲存事件,AppendEvent也將立即重播的事件。
資料表
事件的規則特點:
1. 事件描述了已經發生的事情,因為我們不能改變過去,他們是不可變的。在他們持久後,事件不能改變。
2.擴充套件以前的規則:他們不能被刪除。
3.每一個事件都包含表示狀態變化所需的所有資訊,並能夠重播它。如果他們可以來自其他事件,他們不應該包括計算值。
4.事件的後設資料包含它的型別,當它發生時間
5.您不能查詢事件。它們只用於重放一個給定的時間內系統的狀態。
對於一個事件源物件,一個更改狀態的請求只會導致三個東西中的一個:
1.儲存事件;
2.丟擲一個例外(因為一個業務規則會被侵犯);
3.什麼都不做。
其他的結果,如直接修改物件的狀態或進行資料庫呼叫,都是非常令人沮喪的,因為它們是副作用。副作用通常不會被儲存的事件表示,這意味著你不能重播它們。也使測試更難。
好處
因為你只需要讀取和附加事件,儲存是非常容易的。你需要做的唯一的事情是新增一個事件和檢索事件列表。你可以儲存事件在任何地方:在一個表中,一個鍵值儲存,一個檔案目錄,一個電子郵件,或幾乎任何你可以想象的地方。由於事件是不可變的,他們是非常容易快取,這使您能夠獲得優異的效能。
您將不再需要顯式的事務,因為您只需要插入資料,而不需要更新或刪除它;您也只需要一個表、集合、set,或是您的資料儲存組資料。事務就像一把鎖,所以不需要事務是一種像無鎖程式碼:沒有死鎖和效能快得多。
您還得到一個免費的審計日誌,您可以在除錯時使用,看看到底發生了什麼,為什麼系統處於一個特定的狀態。
如果您確定實體類中的操作是無副作用得,它基本上生活在隔離中,類Class將有一個非常低的耦合度量。你已經從系統的其餘部分中分離出了業務邏輯。
如果你已經實現了這些分離,測試你的邏輯可以表示為“指定的這些事件發生了,當這個操作被請求,那麼這些事件是否被儲存?”或者,“那麼這個異常會被丟擲嗎?”
你不必處理建立在代數上的關聯式資料庫,因為你的物件不是。事件溯源迴避了物件關係阻抗匹配。
你的真理之源是一個資料庫中的一堆事件。在你儲存它們之後,你也可以透過一個訊息系統廣播到世界各地。它很容易讓其他應用程式知道在你的系統中發生了什麼。您不再需要到另一個系統儲存中查詢資料,然後複製以找出發生了什麼變化。所有您需要儲存的是:一個唯一物件識別符號的列表和他們的最新已知的版本號。
缺點
當然,每一個方法的缺點,事件溯源也不例外。最重要的實際缺點是:沒有簡單的方法來檢視您的應用程式的持久狀態是什麼,你不能透過查詢得知。只有當你只能看到執行時發生了什麼時。一個解決方案,是使用一個單獨的模型讀取發生在您的系統中的事件,由此需要CQRS。
事件溯源是一個完全不同於大多數人都習慣的方法。您需要定義可以在您的系統中發生的每一個事件,所以新增新的功能可能會比過去習慣的慢。為了彌補這一差距,你的資料變得更珍貴。
事件溯源聽起來像是一個非常低效的機制來儲存狀態。它比當前狀態直接儲存需要更多的儲存空間。它還需要更多的處理時間,僅僅是因為需要檢索更多的資料才能到達當前狀態。因為你現在擁有幾乎無限量的儲存和處理能力,克服這些不足是很容易的。其中之一是使用快照。
快照
你可能會認為,當一個事件流包含數千或數萬的事件時,您的系統必須變得緩慢,因為每一個操作需要重播所有這些事件。嗯,不一定。
記住,唯一做回放事件是由於要改變有狀態。它應該是冪等;無論你重播事件一次或一百次,它應該有相同的結局。
我們重播以後,比如一萬事件,我們可以得到結果狀態的快照並儲存,以最後一個重播事件為標識。現在,當我們想要當前狀態時,我們只需載入快照和回放快照以後任何發生的事件,如果你已經正確建立了你的快照,為了重放所有的事件載入快照和重放新的事件應該是相同。
測試
前面提到過測試變得容易多了。讓我們更具體一點,展示如何測試一個事件溯源物件。
private AppointmentCreated CreateAppointmentCreatedEvent() { return new AppointmentCreated( appointmentId: Guid.NewGuid(), startTime: DateTimeOffset.Now.AddHours(3), endTime: DateTimeOffset.Now.AddHours(4), title: "Appointment"); } private AppointmentRenamed CreateAppointmentRenamedEvent() { return new AppointmentRenamed(title: "Renamed appointment"); } private Appointment CreateSut(IEnumerable<IEvent> events) { var sut = new Appointment(); foreach (var @event in events) sut.ReplayEvent(@event); return sut; } <p class="indent">[Fact] public void Reschedule_AppendsAppointmentRescheduled() { // GIVEN these events have happened var events = new IEvent[] { CreateAppointmentCreatedEvent(), CreateAppointmentRenamedEvent() }; var sut = CreateSut(events); // WHEN we ask to reschedule var newStartTime = DateTimeOffset.Now.AddHours(5); var newEndTime = DateTimeOffset.Now.AddHours(6); sut.Reschedule(newStartTime, newEndTime); // THEN does the AppointmentRescheduledEvent get published? sut.AppendedEvents.ShouldAllBeEquivalentTo( new[] { new AppointmentRescheduled(newStartTime, newEndTime) }, config => config.RespectingRuntimeTypes()); } <p class="indent"> |
請注意,測試可以分為三個邏輯部分:給定的一些狀態,什麼時候一個動作執行,那麼我們可以觀察到這種行為嗎?這種測試正式的語言為:Cucumber 。
類比
事件溯源是非常強大的思維方式,但它不真的是新的。我們大多數人都是熟悉的系統中都存在它。一些例子:
1.您的銀行帳戶可能儲存使用事件溯源。當前賬戶餘額只是你所做的所有存款和取款的最終結果。
2.版本控制系統。每個提交或更改到檔案都是一個事件。如果您重播所有的事件,您將得到原始碼的當前狀態。
3.大多數大型RDBM關聯式資料庫內部使用事件溯源。簡單地說,只有三個事件:插入,更新和刪除。RDBMS儲存事件在事務日誌中並將其運用到資料表操作上。
相關文章
- MySQL的事件溯源Event Sourcing表結構MySql事件
- 事件溯源不是什麼?事件
- 事件迴圈Event loop到底是什麼事件OOP
- 事件驅動“Event-Driven”是什麼意思?事件
- [DOM Event Learning] Section 2 概念梳理 什麼是事件 DOM Event事件
- 為什麼Event Sourcing是一種微服務通訊反模式 - Oliver Libutzki微服務模式
- WIX是如何從CRUD轉換到Event Sourcing?
- 如何讓客戶方便地使用事件溯源?事件溯源有什麼好處?- daryush_d事件
- 說服您的CTO使用事件溯源 -Event Store Blog事件
- Apache Kafka不適用於Event Sourcing!ApacheKafka
- 什麼是jquery事件冒泡jQuery事件
- 區塊鏈溯源技術是什麼?區塊鏈溯源技術開發區塊鏈
- 為什麼要有事件迴圈機制(Event Loop)事件OOP
- 事件協作和事件溯源事件
- 事件流與事件溯源事件
- PHP 事件溯源PHP事件
- 什麼是事件門戶?- solace事件
- 8.2 什麼是事件主題事件
- akka-typed(10) - event-sourcing, CQRS實戰
- Event Sourcing在分散式系統中應用分散式
- Event-Sourcing+CQRS的Spring原始碼案例Spring原始碼
- 用大白話告訴你什麼是Event LoopOOP
- javascript事件捕獲是什麼意思JavaScript事件
- java事件處理模型是什麼Java事件模型
- 剖玄析微聚合 - 事件溯源事件
- 為什麼創業公司反而適合使用微服務+事件溯源? -zimarev創業微服務事件
- LMAX+Event Sourcing架構的一些疑惑架構
- 事件溯源:是來自事件的狀態與作為狀態的事件? - verraes事件
- 為什麼我們放棄使用Kafka Streams實現全部的事件溯源?-MateuszKafka事件
- v$session - 你看到的event真的是session當前的等待事件麼?Session事件
- 資料庫變更之後Event Sourcing怎樣重放?資料庫
- 事件溯源全指南 - Arkwrite事件
- mysql 事件 eventMySql事件
- event事件(1)事件
- 事件消費者之 Projector - 事件溯源事件Project
- 事件消費者之 Reactor - 事件溯源事件React
- (譯)使用Spring Boot和Axon實現CQRS&Event SourcingSpring Boot
- GitHub - knative/eventing-contrib: 基於knative的Event Sources事件溯源Github事件