AxonFramework,快照(Snapshotting) 轉

當聚合存活很長一段時間,它們的狀態不斷變化,它們會生成大量的事件。不得不加載所有這些事件去復原一個聚合的狀態,可能會有很大的性能影響。快照事件是一個有着特殊用途的領域事件:它將任意數量的事件歸納爲單個事件。通過定期創建和存儲快照事件,事件存儲不必返回長的事件列表。只返回最後一個快照事件和在快照之後發生的所有事件

例如,庫存物品往往會經常變化。每銷售一件物品,事件就減少一件庫存。每次一批新物品進來,庫存就增加一些。如果你每天銷售一百件,你每天會產生至少100個事件。幾天之後,你的系統將會花太多的時間讀取所有這些事件,只是爲了弄清楚它是否應該raise一個“ItemOutOfStockEvent”。單個快照事件僅僅通過存儲當前的庫存數量就可以取代很多這些事件。

Creating a snapshot創建一個快照

快照的創建可由多種因素觸發,例如,從上次快照創建以來的事件的數量,初始化一個聚合的時候超過了某個閾值,基於時間的,等等。目前,Axon提供了一種機制,允許你基於事件計數閾值觸發快照。

當要創建快照時的定義,由SnapshotTriggerDefinition接口提供。

當加載聚合所需的事件數量超過一定的閾值時,EventCountSnapshotTriggerDefinition提供觸發快照創建的機制。如果加載一個聚合需要的事件的數量超過某個可配置的閾值,觸發器告訴Snapshotter爲聚合創建一個快照。

快照觸發器在一個事件溯源存儲庫上配置,並有很多屬性允許你調整觸發:

  • 快照設置實際的快照實例,負責創建和存儲實際的快照事件;
  • 觸發器設置觸發快照創建的閾值;

Snapshotter負責快照的實際創建。通常,快照是一個應該儘可能少的擾亂操作進程的進程。因此,建議在不同的線程運行Snapshotter。Snapshotter接口聲明瞭單獨的方法:scheduleSnapshot(),以聚合的類型和標識符作爲參數。

Axon提供了AggregateSnapshotter,它創建並存儲AggregateSnapshot實例。這是一種特殊類型的快照,因爲它包含了在它內部的實際的聚合實例。Axon提供的存儲庫知道這種類型的快照,並從它提取聚合,而不是實例化一個新的。快照事件之後加載的所有事件傳輸到取出的聚合實例。

注意
確保你使用的序列化器實例(默認爲XStreamSerializer)是能夠序列化你的聚合的。XStreamSerializer要求使用Hotspot JVM,或者你的聚合要有一個可訪問的默認的構造函數或實現Serializable接口。

AbstractSnapshotter提供了一組基本的屬性,允許你調整創建快照的方式:

  • EventStore設置事件存儲,用於加載過去的事件和存儲快照。這個事件存儲必須實現SnapshotEventStore接口。
  • Executor設計executor,比如ThreadPoolExecutor提供了線程來處理實際快照的創建。默認情況下,快照的創建是在線程中調用scheduleSnapshot()方法,一般不建議用於生產。

AggregateSnapshotter提供另一個屬性:

  • AggregateFactories是允許你設置創建聚合實例工廠的屬性。配置多個聚合工廠允許你使用一個單獨的Snapshotter爲各種聚合類型創建快照。EventSourcingRepository實現提供了訪問他們使用的AggregateFactory。這可以用於配置相同的聚合工廠像在存儲庫中使用的Snapshotter一樣。

注意
如果你使用一個executor在另一個線程中執行快照創建,如果必要的話,確保你爲潛在的事件存儲配置正確的事務管理。
Spring用戶可以使用SpringAggregateSnapshotter,當需要創建一個快照時,它將從應用程序上下文自動查找合適的AggregateFactory。

存儲快照事件

當快照存儲在事件存儲中時,它會自動使用快照歸納所有之前的事件並將其返回到它們的位置。所有事件存儲實現允許併發創建快照。這意味着它們允許快照被存儲的同時,另一個進程爲同一個聚合添加事件。這允許快照進程作爲一個完全獨立進程。

注意
通常情況下,一旦它們是快照事件的一部分,你就可以歸檔所有的事件。快照事件將永遠不會在常規操作場景中再次讀取事件存儲。然而,如果你希望能夠重建快照創建前一刻的聚合狀態,你必須保持事件爲最新。

Axon提供了一種特殊類型的快照事件:AggregateSnapshot,它將整個聚合存儲爲一個快照。動機很簡單:你的聚合應該只包含與業務決策相關的的狀態。這正是你想要在一個快照中捕獲的信息。所有事件溯源存儲庫由Axon承認的AggregateSnapshot提供,並將從它提取的聚合。注意,使用這個快照事件要求事件序列化機制需要能夠對聚合進行序列化。

根據快照事件初始化聚合

快照事件是一個和其他事件一樣的事件。這意味着一個快照事件就像任何其他領域事件一樣被處理。當使用註解來劃分事件處理程序(@EventHandler)時,你可以註解一個方法,基於快照事件初始化全部的聚合狀態。下面的代碼示例演示了,如何像對待任何其他聚合中的領域事件一樣對待快照事件。

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。這種類型的快照事件包含實際的聚合。聚合工廠識別這種類型的事件並從快照中提取聚合。然後,將所有其他事件重新應用到提取的快照。這意味着聚合從不需要能夠處理AggregateSnapshot實例自身。

先進的衝突檢測和解決方案

明確改變的含義作爲一個主要的優勢,就是你可以更精確地檢測衝突的變化。通常,這些衝突的變化,發生在兩個用戶同時處理相同的數據(幾乎)時。想象一下兩個用戶都查看一個特定版本的數據。他們都決定對這些數據進行修改。他們都將發送一個命令就像“在這個聚合的X版本上,那樣做”,其中X是聚合的預期版本。其中一個會將修改實際應用於預期的版本。另一個用戶不會。

當聚合已經被另一個進程修改時,你可以檢查用戶的意圖與任何看不見的修改是否衝突,而不是簡單地拒絕所有傳入命令。
檢測衝突,傳遞一個ConflictResolver類型的參數到你的聚合的 @CommandHandler方法。這個接口提供了detectConflicts方法,允許你在執行特定類型的命令時,定義被認爲是衝突的事件類型。

注意
注意ConflictResolver只會包含任何潛在的衝突事件,如果聚合用一個預期的版本加載。使用@TargetAggregateVersion在一個命令的字段上標示聚合的預期的版本。

如果找到事件匹配的斷言(predicate),拋出異常(detectConflicts可選的第二個參數允許你定義拋出的異常)。如果沒有找到,處理將繼續正常進行。

如果沒有調用detectConflicts,並有潛在衝突的事件,@CommandHandler將失敗。這可能是提供一個預期的版本的情況下,在@CommandHandler方法的參數中沒有可用的ConflictResolver 。



作者:勇赴
鏈接:http://www.jianshu.com/p/0cf9c4c0e037
來源:簡書
著作權歸作者所有。商業轉載請聯繫作者獲得授權,非商業轉載請註明出處。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章