2016-07-25 51 views
0

事件採購,你存儲所有的個人域事件已經發生了一個總結例如,稱爲事件流。隨着事件流你還存儲一個流版本流版本在事件採購

如若每個域事件有關,或者它應該事務變化(又名命令)有關?


實施例:

我們當前的事件存儲的狀態是:

aggregate_id | version | event 
-------------|---------|------ 
1   | 1  | E1 
1   | 2  | E2 

一個新命令在骨料1執行該命令產生兩個新事件E3和E4。

方法1:

aggregate_id | version | event 
-------------|---------|------ 
1   | 1  | E1 
1   | 2  | E2 
1   | 3  | E3 
1   | 4  | E4 

用這種方法樂觀併發可以通過存儲機制使用唯一索引,但重播事件,直到3版本可以離開合計/系統處於不一致的狀態來完成。

方法2:

aggregate_id | version | event 
-------------|---------|----- 
1   | 1  | E1 
1   | 2  | E2 
1   | 3  | E3 
1   | 3  | E4 

重播事件,直到3版本離開合計/系統處於一致的狀態。

謝謝!

+0

我還在研究兩種方法的優缺點。還要檢查在IDDD和其他DDD書籍中使用哪種方法。 – martinezdelariva

回答

1

簡短回答:#1。

事件E3和E4的寫入應該是同一事務的一部分。

請注意,這兩種方法在您關心的情況下並沒有真正的不同。如果你在第一種情況下閱讀可能會錯過E4,那麼你可以在第二種情況下閱讀。在你正在加載聚合進行寫入的用例中;加載前三個事件會告訴你下一個版本應該是#4。

在方法#1的情況下,嘗試寫入版本4會產生唯一的約束衝突;命令處理程序將無法確定問題是否是數據加載不正常,或者僅僅是一種樂觀併發失敗,但是在任何情況下,結果都不會寫入,並且記錄簿仍處於一致狀態。

在方法#2的情況下,嘗試寫入版本4不會與任何內容發生衝突。寫入成功,現在您的E5與E4不一致。 Bleah。

有關架構事件商店引用,你可能會考慮檢討:

我首選的方案,假設你是被迫滾你擁有,將流與事件分開。

stream_id | sequence | event_id 
-------------|----------|------ 
1   | 1  | E1 
1   | 2  | E2 

流爲您提供了一個過濾器(流ID)來識別你想要的事件和順序(順序),以確保您閱讀是在同一順序爲你寫的事件的事件。但除此之外,這是一種人爲的事情,是我們碰巧選擇我們總體邊界的一種副作用。所以它的角色應該相當有限。

實際的事件數據,住在別的地方。

event_id | data | meta_data | ... 
-------------------------------------- 
E1  | ... | ... | ... 
E2  | ... | ... | ... 

如果您需要能夠識別與特定命令相關的事件,這就是事件的元數據,流歷史(見的correlationID,causationId)的不屬於的一部分。

0

方法1是我所使用和看到其他人使用 - 只是一個遞增號碼的情況下,通常被稱爲EventNumber

樂觀併發部分只是這樣,當你加載你的骨料,你知道是什麼最近的事件是。然後處理命令並保存所有結果事件 - 如果您看到任何超出您加載的數字的數字,則意味着您已經過時並可以採取相應措施,否則您可以保存事件。

+0

雖然我不是真的將這個概念看作是與版本控制相關的命令,但是您希望能夠知道每個命令處理的聚合狀態嗎? I.e命令1產生1個事件,命令2產生3個事件,但'版本'是2而不是4? – tomliversidge

2

沒有什麼能阻止你引入commit_sequence以及version

例如,在NEventStore中,您可以看到提交的StreamRevision(對於每個事件都增加版本)和CommitSequence

0

在與事件採購領域驅動設計,集合表示一致性邊界,並且其不變量必須是在每個命令(或函數調用)的開始和結束真。你可以在成員函數的中間違反一個不變量,只要它在結束時沒有被違反。

你在帖子中指出的是非常有見地的。也就是說,如果聚集中的單個命令(或者調用成員函數)產生多個事件,那麼當另一個進程從磁盤重新加載聚合時,僅存儲其中一些事件可能會導致違反您的不變量。將SQL數據庫用作事件存儲時,會出現許多可能導致此問題的相關方案。

避免這種情況的第一種(也是最簡單的)方法是將所有事件INSERT語句包裝到一個事務中,以便所有事件都被保留或者沒有任何事件(例如,由於併發)。這樣,你的「在磁盤上」不變的表示就被保留下來了。你還必須確保你的事務隔離級別不是「READ UNCOMMITTED」(這樣其他進程看不到你提交的一半)。您還必須確保數據庫不會在進程之間「交錯」事件序列號。例如,數據庫爲進程A中的事件分配序號「1」,爲進程B中的事件分配序號「2」,然後再次爲進程A中的第二事件分配序號「3」。所有事件都可以提交到數據庫,因爲併發約束(聚合ID +事件序列號)沒有衝突,但事件序列是由兩個進程編寫的,因此您的不變量可能仍然被違反。

第二種選擇是將所有事件包裝到一個數組中,該數組用一個INSERT語句持久化。這基本上會導致每個提交都有一個版本號,而不是每個事件的版本號。對我來說,這更合乎邏輯,但它需要你有一個過程,在將事件數組發送給各種事件處理程序和進程管理器之前,先「解除」事件數組。我個人在一個項目中使用第二種機制,該項目將原始二進制格式的事件存儲在磁盤上。事件本身僅包含聚合更改狀態所需的最少量信息 - 事件不包含聚合標識。另一方面,提交確實包含聚集標識符,提交序列號和各種其他元數據。這基本上將聚合體之間的功能分離爲用於未提交事件的處理程序用於提交事件的事件處理程序。這種區分也是有道理的,因爲如果某個事件是「事實」 - 發生了某種事情 - 那麼集合的作用和集合是否實際上保存到磁盤上的內容是有區別的。

從理論上說,您的問題的一個很好的例子是鏈接列表 - 只考慮內存中的表示形式:磁盤上沒有持久性。你在鏈接列表或數組上使用鏈表的原因之一是它允許有效地插入節點(當然,比數組更有效)。插入操作通常要求將當前節點的「下一個」指針設置爲新節點的內存地址,並將新節點的「下一個」指針設置爲當前節點的前一個「下一個」指針。如果另一個進程在完成第一個操作之後但在第二個操作完成之前正在讀取內存中的同一個鏈接列表,則不會看到鏈接列表中的所有節點。如果每個「操作」就像一個「事件」,那麼只有看到第一個事件纔會導致讀者看到一個斷鏈表。