Getting state
Event sequence state provides information about how far a sequence has progressed. The most common state value is the tail sequence number, which represents the latest event appended to the sequence. Use the IEventSequence APIs, such as GetTailSequenceNumber and related state calls, to capture the current position.
Use sequence state for scenarios such as:
- Tracking progress for consumers and observers
- Capturing a point in time for read model time travel
- Avoiding duplicate processing when resuming work
For point-in-time reads of read models, capture the sequence position from the event sequence state and use it alongside the read model APIs described in the read models guides.
Related reading:
Examples
Section titled “Examples”Capture the tail for a checkpoint
Section titled “Capture the tail for a checkpoint”using Cratis.Chronicle.Events;using Cratis.Chronicle.EventSequences;
public class GettingStateCheckpointStore(IEventLog eventLog){ public async Task<EventSequenceNumber> CaptureTail() { // Persists the current tail so processing can resume later. return await eventLog.GetTailSequenceNumber(); }}import io.cratis.chronicle.IEventStore
suspend fun getAccountTailSequenceNumber(store: IEventStore, accountId: String): Long { val tail = store.eventLog.getTailSequenceNumber(accountId) println("Tail sequence number for $accountId: ${tail.value}") return tail.value}import io.cratis.chronicle.EventStore;
import io.cratis.chronicle.java.EventLogJavaBridge;
class EventsGettingStateTail { long getAccountTailSequenceNumber(EventStore store, String accountId) { long tail = EventLogJavaBridge.getTailSequenceNumber(store.getEventLog(), accountId); System.out.println("Tail sequence number for " + accountId + ": " + tail); return tail; }}defmodule MyApp.GettingStateCheckpointStore do def capture_tail do # Persists the current tail so processing can resume later. Chronicle.get_tail_sequence_number() endendimport { EventSequenceNumber, IEventStore } from '@cratis/chronicle';
class GettingStateCheckpointStore { constructor(private readonly store: IEventStore) {}
async captureTail(): Promise<EventSequenceNumber> { // Persists the current tail so processing can resume later. return await this.store.eventLog.getTailSequenceNumber(); }}Kotlin and Java don’t currently expose a way to read the tail sequence number at all — their public event sequence APIs only support appending and checking for existence.
Capture the tail for a specific event source and event types
Section titled “Capture the tail for a specific event source and event types”using Cratis.Chronicle.Events;using Cratis.Chronicle.EventSequences;
[EventType]public record GettingStateInventoryAdjusted(string Sku, int Delta);
[EventType]public record GettingStateInventoryReserved(string Sku, int Quantity);
public class GettingStateInventoryCheckpoint(IEventLog eventLog){ public async Task<EventSequenceNumber> CaptureFor(EventSourceId inventoryId) { // Scopes the tail to a specific stream of inventory events. var eventTypes = new[] { typeof(GettingStateInventoryAdjusted).GetEventType(), typeof(GettingStateInventoryReserved).GetEventType() };
return await eventLog.GetTailSequenceNumber( eventSourceId: inventoryId, filterEventTypes: eventTypes ); }}import io.cratis.chronicle.IEventStoreimport io.cratis.chronicle.eventSequences.EventSequenceNumber
/** * Scopes the tail sequence number to a specific event source, rather than the whole event log. */suspend fun captureFor(store: IEventStore, inventoryId: String): EventSequenceNumber = store.eventLog.getTailSequenceNumber(eventSourceId = inventoryId)import io.cratis.chronicle.EventStore;
import io.cratis.chronicle.java.EventLogJavaBridge;
class EventsGettingStateTailForEventSource { // Scopes the tail sequence number to a specific event source, rather than the whole event log. long captureFor(EventStore store, String inventoryId) { return EventLogJavaBridge.getTailSequenceNumber(store.getEventLog(), inventoryId); }}Elixir does not support this workflow yet.import { eventType, EventSequenceNumber, IEventLog } from '@cratis/chronicle';
@eventType()class GettingStateInventoryAdjusted { constructor(readonly sku: string, readonly delta: number) {}}
@eventType()class GettingStateInventoryReserved { constructor(readonly sku: string, readonly quantity: number) {}}
class GettingStateInventoryCheckpoint { constructor(private readonly eventLog: IEventLog) {}
// Scopes the tail to a specific stream of inventory events. captureFor(inventoryId: string): Promise<EventSequenceNumber> { return this.eventLog.getTailSequenceNumber( inventoryId, undefined, undefined, undefined, [GettingStateInventoryAdjusted, GettingStateInventoryReserved] ); }}This scoped form (filtering by event source and event types) is currently C#-only. Elixir and TypeScript can capture the overall tail (see above), but neither exposes a way to scope it to specific event types.
Check whether an observer is caught up
Section titled “Check whether an observer is caught up”using Cratis.Chronicle.Events;using Cratis.Chronicle.EventSequences;
public class GettingStateObserverProgress(IEventSequence eventSequence){ public async Task<EventSequenceNumber> GetRelevantTail(Type observerType) { // Uses the observer's event type filters to compute the relevant tail. return await eventSequence.GetTailSequenceNumberForObserver(observerType); }}import io.cratis.chronicle.eventSequences.EventSequenceNumberimport io.cratis.chronicle.eventSequences.IEventSequenceimport kotlin.reflect.KClass
/** * Computes the tail sequence number relevant to a specific observer, based on the event types * it handles. */suspend fun getRelevantTail(eventSequence: IEventSequence, observerType: KClass<*>): EventSequenceNumber = eventSequence.getTailSequenceNumberForObserver(observerType)import io.cratis.chronicle.eventSequences.IEventLog;import io.cratis.chronicle.java.EventLogJavaBridge;
class TailForObserverExample { /** * Computes the tail sequence number relevant to a specific observer, based on the event types * it handles. */ static long getRelevantTail(IEventLog eventLog, Class<?> observerType) { return EventLogJavaBridge.getTailSequenceNumberForObserver(eventLog, observerType); }}defmodule MyApp.Events.GettingStateObserverProgressSomethingHappened do use Chronicle.Events.EventType, id: "getting-state-observer-progress-something-happened"
defstruct []end
defmodule MyApp.Reactors.GettingStateObserverProgressReactor do use Chronicle.Reactors.Reactor
alias MyApp.Events.GettingStateObserverProgressSomethingHappened
@handles GettingStateObserverProgressSomethingHappened
@impl true def handle(%GettingStateObserverProgressSomethingHappened{}, _context), do: :okend
defmodule MyApp.GettingStateObserverProgress do alias Chronicle.EventSequences.EventLog alias MyApp.Reactors.GettingStateObserverProgressReactor
def get_relevant_tail do # Uses the reactor's @handles event types to compute the relevant tail. EventLog.get_tail_sequence_number_for_observer(GettingStateObserverProgressReactor) endendimport { Constructor } from '@cratis/fundamentals';import { EventSequenceNumber, IEventSequence } from '@cratis/chronicle';
class GettingStateObserverProgress { constructor(private readonly eventSequence: IEventSequence) {}
// Uses the observer's event type filters to compute the relevant tail. getRelevantTail(observerType: Constructor): Promise<EventSequenceNumber> { return this.eventSequence.getTailSequenceNumberForObserver(observerType); }}Computing the relevant tail for a specific observer is currently a C#-only capability.