當(dāng)聚合存活很長一段時(shí)間,它們的狀態(tài)不斷變化,它們會(huì)生成大量的事件。不得不加載所有這些事件去復(fù)原一個(gè)聚合的狀態(tài),可能會(huì)有很大的性能影響??煺帐录且粋€(gè)有著特殊用途的領(lǐng)域事件:它將任意數(shù)量的事件歸納為單個(gè)事件。通過定期創(chuàng)建和存儲(chǔ)快照事件,事件存儲(chǔ)不必返回長的事件列表。只返回最后一個(gè)快照事件和在快照之后發(fā)生的所有事件
例如,庫存物品往往會(huì)經(jīng)常變化。每銷售一件物品,事件就減少一件庫存。每次一批新物品進(jìn)來,庫存就增加一些。如果你每天銷售一百件,你每天會(huì)產(chǎn)生至少100個(gè)事件。幾天之后,你的系統(tǒng)將會(huì)花太多的時(shí)間讀取所有這些事件,只是為了弄清楚它是否應(yīng)該raise一個(gè)“ItemOutOfStockEvent”。單個(gè)快照事件僅僅通過存儲(chǔ)當(dāng)前的庫存數(shù)量就可以取代很多這些事件。
Creating a snapshot創(chuàng)建一個(gè)快照
快照的創(chuàng)建可由多種因素觸發(fā),例如,從上次快照創(chuàng)建以來的事件的數(shù)量,初始化一個(gè)聚合的時(shí)候超過了某個(gè)閾值,基于時(shí)間的,等等。目前,Axon提供了一種機(jī)制,允許你基于事件計(jì)數(shù)閾值觸發(fā)快照。
當(dāng)要?jiǎng)?chuàng)建快照時(shí)的定義,由SnapshotTriggerDefinition接口提供。
當(dāng)加載聚合所需的事件數(shù)量超過一定的閾值時(shí),EventCountSnapshotTriggerDefinition提供觸發(fā)快照創(chuàng)建的機(jī)制。如果加載一個(gè)聚合需要的事件的數(shù)量超過某個(gè)可配置的閾值,觸發(fā)器告訴Snapshotter為聚合創(chuàng)建一個(gè)快照。
快照觸發(fā)器在一個(gè)事件溯源存儲(chǔ)庫上配置,并有很多屬性允許你調(diào)整觸發(fā):
- 快照設(shè)置實(shí)際的快照實(shí)例,負(fù)責(zé)創(chuàng)建和存儲(chǔ)實(shí)際的快照事件;
- 觸發(fā)器設(shè)置觸發(fā)快照創(chuàng)建的閾值;
Snapshotter負(fù)責(zé)快照的實(shí)際創(chuàng)建。通常,快照是一個(gè)應(yīng)該盡可能少的擾亂操作進(jìn)程的進(jìn)程。因此,建議在不同的線程運(yùn)行Snapshotter。Snapshotter接口聲明了單獨(dú)的方法:scheduleSnapshot(),以聚合的類型和標(biāo)識(shí)符作為參數(shù)。
Axon提供了AggregateSnapshotter,它創(chuàng)建并存儲(chǔ)AggregateSnapshot實(shí)例。這是一種特殊類型的快照,因?yàn)樗嗽谒鼉?nèi)部的實(shí)際的聚合實(shí)例。Axon提供的存儲(chǔ)庫知道這種類型的快照,并從它提取聚合,而不是實(shí)例化一個(gè)新的。快照事件之后加載的所有事件傳輸?shù)饺〕龅木酆蠈?shí)例。
注意
確保你使用的序列化器實(shí)例(默認(rèn)為XStreamSerializer)是能夠序列化你的聚合的。XStreamSerializer要求使用Hotspot JVM,或者你的聚合要有一個(gè)可訪問的默認(rèn)的構(gòu)造函數(shù)或?qū)崿F(xiàn)Serializable接口。
AbstractSnapshotter提供了一組基本的屬性,允許你調(diào)整創(chuàng)建快照的方式:
- EventStore設(shè)置事件存儲(chǔ),用于加載過去的事件和存儲(chǔ)快照。這個(gè)事件存儲(chǔ)必須實(shí)現(xiàn)SnapshotEventStore接口。
- Executor設(shè)計(jì)executor,比如ThreadPoolExecutor提供了線程來處理實(shí)際快照的創(chuàng)建。默認(rèn)情況下,快照的創(chuàng)建是在線程中調(diào)用scheduleSnapshot()方法,一般不建議用于生產(chǎn)。
AggregateSnapshotter提供另一個(gè)屬性:
- AggregateFactories是允許你設(shè)置創(chuàng)建聚合實(shí)例工廠的屬性。配置多個(gè)聚合工廠允許你使用一個(gè)單獨(dú)的Snapshotter為各種聚合類型創(chuàng)建快照。EventSourcingRepository實(shí)現(xiàn)提供了訪問他們使用的AggregateFactory。這可以用于配置相同的聚合工廠像在存儲(chǔ)庫中使用的Snapshotter一樣。
注意
如果你使用一個(gè)executor在另一個(gè)線程中執(zhí)行快照創(chuàng)建,如果必要的話,確保你為潛在的事件存儲(chǔ)配置正確的事務(wù)管理。
Spring用戶可以使用SpringAggregateSnapshotter,當(dāng)需要?jiǎng)?chuàng)建一個(gè)快照時(shí),它將從應(yīng)用程序上下文自動(dòng)查找合適的AggregateFactory。
存儲(chǔ)快照事件
當(dāng)快照存儲(chǔ)在事件存儲(chǔ)中時(shí),它會(huì)自動(dòng)使用快照歸納所有之前的事件并將其返回到它們的位置。所有事件存儲(chǔ)實(shí)現(xiàn)允許并發(fā)創(chuàng)建快照。這意味著它們允許快照被存儲(chǔ)的同時(shí),另一個(gè)進(jìn)程為同一個(gè)聚合添加事件。這允許快照進(jìn)程作為一個(gè)完全獨(dú)立進(jìn)程。
注意
通常情況下,一旦它們是快照事件的一部分,你就可以歸檔所有的事件??煺帐录⒂肋h(yuǎn)不會(huì)在常規(guī)操作場景中再次讀取事件存儲(chǔ)。然而,如果你希望能夠重建快照創(chuàng)建前一刻的聚合狀態(tài),你必須保持事件為最新。
Axon提供了一種特殊類型的快照事件:AggregateSnapshot,它將整個(gè)聚合存儲(chǔ)為一個(gè)快照。動(dòng)機(jī)很簡單:你的聚合應(yīng)該只包含與業(yè)務(wù)決策相關(guān)的的狀態(tài)。這正是你想要在一個(gè)快照中捕獲的信息。所有事件溯源存儲(chǔ)庫由Axon承認(rèn)的AggregateSnapshot提供,并將從它提取的聚合。注意,使用這個(gè)快照事件要求事件序列化機(jī)制需要能夠?qū)酆线M(jìn)行序列化。
根據(jù)快照事件初始化聚合
快照事件是一個(gè)和其他事件一樣的事件。這意味著一個(gè)快照事件就像任何其他領(lǐng)域事件一樣被處理。當(dāng)使用注解來劃分事件處理程序(@EventHandler)時(shí),你可以注解一個(gè)方法,基于快照事件初始化全部的聚合狀態(tài)。下面的代碼示例演示了,如何像對(duì)待任何其他聚合中的領(lǐng)域事件一樣對(duì)待快照事件。
public class MyAggregate extends AbstractAnnotatedAggregateRoot {
// ... code omitted for brevity
@EventHandler
protected void handleSomeStateChangeEvent(MyDomainEvent event) {
// ...
}
@EventHandler
protected void applySnapshot(MySnapshotEvent event) {
// the snapshot event should contain all relevant state
this.someState = event.someState;
this.otherState = event.otherState;
}
}
有一種類型的快照事件處理方式不同:AggregateSnapshot。這種類型的快照事件包含實(shí)際的聚合。聚合工廠識(shí)別這種類型的事件并從快照中提取聚合。然后,將所有其他事件重新應(yīng)用到提取的快照。這意味著聚合從不需要能夠處理AggregateSnapshot實(shí)例自身。
先進(jìn)的沖突檢測和解決方案
明確改變的含義作為一個(gè)主要的優(yōu)勢,就是你可以更精確地檢測沖突的變化。通常,這些沖突的變化,發(fā)生在兩個(gè)用戶同時(shí)處理相同的數(shù)據(jù)(幾乎)時(shí)。想象一下兩個(gè)用戶都查看一個(gè)特定版本的數(shù)據(jù)。他們都決定對(duì)這些數(shù)據(jù)進(jìn)行修改。他們都將發(fā)送一個(gè)命令就像“在這個(gè)聚合的X版本上,那樣做”,其中X是聚合的預(yù)期版本。其中一個(gè)會(huì)將修改實(shí)際應(yīng)用于預(yù)期的版本。另一個(gè)用戶不會(huì)。
當(dāng)聚合已經(jīng)被另一個(gè)進(jìn)程修改時(shí),你可以檢查用戶的意圖與任何看不見的修改是否沖突,而不是簡單地拒絕所有傳入命令。
檢測沖突,傳遞一個(gè)ConflictResolver類型的參數(shù)到你的聚合的 @CommandHandler方法。這個(gè)接口提供了detectConflicts方法,允許你在執(zhí)行特定類型的命令時(shí),定義被認(rèn)為是沖突的事件類型。
注意
注意ConflictResolver只會(huì)包含任何潛在的沖突事件,如果聚合用一個(gè)預(yù)期的版本加載。使用@TargetAggregateVersion在一個(gè)命令的字段上標(biāo)示聚合的預(yù)期的版本。
如果找到事件匹配的斷言(predicate),拋出異常(detectConflicts可選的第二個(gè)參數(shù)允許你定義拋出的異常)。如果沒有找到,處理將繼續(xù)正常進(jìn)行。
如果沒有調(diào)用detectConflicts,并有潛在沖突的事件,@CommandHandler將失敗。這可能是提供一個(gè)預(yù)期的版本的情況下,在@CommandHandler方法的參數(shù)中沒有可用的ConflictResolver 。