日志记录的粒度来自 JournaledGrain<TGrainState,TEventBase>,并包括以下类型参数:
-
TGrainState
表示粒度的状态。 它必须是具有公共默认构造函数的类。 -
TEventBase
是可为此粒度引发的所有事件的常见超类型,可以是任何类或接口。
所有状态和事件对象都应可序列化,因为日志一致性提供程序可能需要持久保存它们和/或在通知消息中发送它们。
对于事件为 POCO(普通旧 C# 对象)的对象,你可以用 JournaledGrain<TGrainState> 作为 JournaledGrain<TGrainState,TEventBase> 的简写形式。
读取粒度状态
若要读取当前粒子状态并确定其版本号,JournaledGrain
具有以下属性:
GrainState State { get; }
int Version { get; }
版本号始终等于已确认事件的总数,状态是将所有确认事件应用到初始状态的结果。 类的默认构造函数 GrainState
确定初始状态,该状态具有版本 0(因为尚未应用任何事件)。
重要: 应用程序不应直接修改由该对象 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 。
当运行时具体调用过渡方法时,这取决于所选日志一致性提供程序及其配置。 除非日志一致性提供程序显式保证它,否则应用程序不应依赖于特定的计时。
某些提供程序(如 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);
但是,并非所有日志一致性提供程序都支持此方法。 如果它不受支持,或者序列的指定段不再可用,则会引发 a NotSupportedException 。
若要检索最新确认版本中所有的事件,请调用:
await RetrieveConfirmedEvents(0, Version);
只能检索确认的事件:如果 toVersion
大于属性的 Version
当前值,则会引发异常。
由于确认的事件永远不会更改,因此即使有多个实例或延迟确认,也无需担心任何竞争条件。 但是,在这种情况下,属性Version
的值在await
恢复时可能会比在RetrieveConfirmedEvents
调用时更大。 因此,建议将其值保存在变量中。 另请参阅有关 并发保证的部分。