JournaledGrain 基础知识

日志记录的粒度来自 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调用时更大。 因此,建议将其值保存在变量中。 另请参阅有关 并发保证的部分。