JournaledGrain 基本概念
Journaled 粒紋衍生自 JournaledGrain<TGrainState,TEventBase>,具有下列型別參數:
TGrainState
代表粒紋的狀態。 其必須是具有公用預設建構函式的類別。TEventBase
是可針對此粒紋引發的所有事件,而且可以是任何類別或介面的常見超型別。
所有狀態和事件物件都應該可序列化 (因為記錄一致性提供者可能需要保存這些項目,並在通知訊息中進行傳送)。
對於事件為 POCO (一般舊 C# 物件) 的粒紋,JournaledGrain<TGrainState> 可以做為 JournaledGrain<TGrainState,TEventBase> 的速記。
讀取粒紋狀態
若要讀取目前的粒紋狀態,並判斷其版本號碼,JournaledGrain 具有屬性
GrainState State { get; }
int Version { get; }
版本號碼一律等於已確認事件的總數,而狀態是將所有已確認事件套用至初始狀態的結果。 初始狀態具有第 0 版 (因為未將任何事件套用至其中),其是由 GrainState 類別的預設建構函式所決定。
重要:應用程式絕對不應該直接修改 State
所傳回的物件。 其僅供讀取。 相對地,當應用程式想要修改狀態,必須藉由引發事件間接執行此動作。
引發事件
引發事件是藉由呼叫 RaiseEvent 語言函式來完成。 例如,代表聊天的粒紋可能會引發 PostedEvent
,以指出使用者提交貼文:
RaiseEvent(new PostedEvent()
{
Guid = guid,
User = user,
Text = text,
Timestamp = DateTime.UtcNow
});
請注意,RaiseEvent
開始寫入儲存體存取,但不會等候寫入完成。 對於許多應用程式而言,請務必等到我們確認事件保存為止。 在此案例下,我們一律會等候 ConfirmEvents 來進行後續作業:
RaiseEvent(new DepositTransaction()
{
DepositAmount = amount,
Description = description
});
await ConfirmEvents();
請注意,即使您未明確呼叫 ConfirmEvents
,事件最終還是會經過確認,其會自動在背景中發生。
狀態轉換方法
每當引發事件時,執行階段會自動更新粒紋狀態。 應用程式不需要在引發事件之後明確更新狀態。 不過,應用程式仍必須提供程式碼,以指定如何更新狀態以回應事件。 有兩種方法可以達到這個目的:
(a) GrainState 類別可以在 StateType
上實作一或多個 Apply
方法。 一般而言,使用者會建立多個多載,並且為事件的執行階段型別選擇最接近的相符項目:
class GrainState
{
Apply(E1 @event)
{
// code that updates the state
}
Apply(E2 @event)
{
// code that updates the state
}
}
(b) 粒紋可以覆寫 TransitionState
語言函式:
protected override void TransitionState(
State state, EventType @event)
{
// code that updates the state
}
轉換方法假設沒有修改狀態物件以外的副作用,而且應該具決定性 (否則,將無法預測效果)。 如果轉換程式碼擲回例外狀況,就會攔截該例外狀況,並包含在由記錄一致性提供者發出之 Orleans 記錄中的警告。
確切來說,執行階段會呼叫轉換方法,取決於所選的記錄一致性提供者及其設定。 除非記錄一致性提供者特別保證,否則應用程式不應該依賴特定時間。
部份提供者,例如 Orleans.EventSourcing.LogStorage 記錄一致性提供者,會在每次載入粒紋時重新執行事件順序。 因此,只要事件物件仍然可以從儲存體正確還原序列化,就可以徹底修改 GrainState
類別和轉換方法。 但對於其他提供者,例如 Orleans.EventSourcing.StateStorage 記錄一致性提供者,只會保存 GrainState
物件,因此開發人員必須確保從儲存體讀取時可以正確還原序列化。
引發多個事件
在呼叫 ConfirmEvents
之前,可以對 RaiseEvent
進行多個呼叫:
RaiseEvent(e1);
RaiseEvent(e2);
await ConfirmEvents();
不過,這可能會造成兩個連續的儲存體存取,在只寫入第一個事件之後,導致粒紋失敗的風險。 因此,一次引發多個事件通常比較好,使用
RaiseEvents(IEnumerable<EventType> events)
這可確保指定的事件序列會以不可部分完成的方式寫入儲存體。 請注意,由於版本號碼一律符合事件序列的長度,因此引發多個事件會一次增加一個以上的版本號碼。
擷取事件序列
基底 JournaledGrain
類別中的下列方法可讓應用程式擷取所有已確認事件序列的指定區段:
Task<IReadOnlyList<EventType>> RetrieveConfirmedEvents(
int fromVersion,
int toVersion);
不過,所有記錄一致性提供者都不提供支援。 如果不支援,或序列的指定區段都不再能夠使用,則會擲回 NotSupportedException。
若要將所有事件擷取到最新確認的版本,則會呼叫
await RetrieveConfirmedEvents(0, Version);
只能擷取確認的事件:如果 toVersion
大於屬性 Version
的目前值,則會擲回例外狀況。
由於確認的事件永遠不會變更,因此即使存在多個執行個體或延遲確認,也不需要擔心。 不過,在這種情況下,當 await
繼續時,屬性 Version
的值可能會比呼叫 RetrieveConfirmedEvents
時還要大,因此建議您在變數中儲存其值。 另請參閱並行保證一節。