using System; using System.Threading; using System.Threading.Tasks; namespace Svrnty.CQRS.Events.Abstractions.Projections; /// /// Stores and retrieves projection checkpoints to track processing progress. /// /// /// /// Phase 7 Feature - Projection Checkpoints: /// Checkpoints enable projections to resume from where they left off after restart /// or failure. Each projection maintains its own checkpoint per stream. /// /// /// Checkpoint Strategy: /// - Checkpoints are updated after successfully processing each batch of events /// - Checkpoint updates should be atomic with read model updates (same transaction) /// - If checkpoint update fails, events will be reprocessed (idempotency required) /// /// public interface IProjectionCheckpointStore { /// /// Gets the current checkpoint for a projection on a specific stream. /// /// The unique name of the projection. /// The name of the stream being consumed. /// Cancellation token. /// /// The checkpoint containing the last processed offset and timestamp, /// or null if the projection has never processed this stream. /// Task GetCheckpointAsync( string projectionName, string streamName, CancellationToken cancellationToken = default); /// /// Saves or updates the checkpoint for a projection on a specific stream. /// /// The checkpoint to save. /// Cancellation token. /// A task representing the async operation. /// /// This should be called after successfully processing a batch of events. /// Ideally, this should be part of the same transaction as the read model update /// to ensure exactly-once processing semantics. /// Task SaveCheckpointAsync( ProjectionCheckpoint checkpoint, CancellationToken cancellationToken = default); /// /// Resets the checkpoint for a projection on a specific stream. /// /// The unique name of the projection. /// The name of the stream being consumed. /// Cancellation token. /// A task representing the async operation. /// /// This is used when rebuilding a projection. After reset, the projection /// will start processing from offset 0. /// Task ResetCheckpointAsync( string projectionName, string streamName, CancellationToken cancellationToken = default); /// /// Gets all checkpoints for a specific projection across all streams. /// /// The unique name of the projection. /// Cancellation token. /// A list of all checkpoints for this projection. /// /// Useful for monitoring projection progress across multiple streams. /// Task GetAllCheckpointsAsync( string projectionName, CancellationToken cancellationToken = default); } /// /// Represents a checkpoint tracking a projection's progress on a stream. /// public sealed record ProjectionCheckpoint { /// /// The unique name of the projection. /// public required string ProjectionName { get; init; } /// /// The name of the stream being consumed. /// public required string StreamName { get; init; } /// /// The last successfully processed offset in the stream. /// /// /// Next time the projection runs, it should start from offset + 1. /// public long LastProcessedOffset { get; init; } /// /// The timestamp when this checkpoint was last updated. /// public DateTimeOffset LastUpdated { get; init; } /// /// The number of events processed by this projection. /// /// /// Useful for monitoring and metrics. /// public long EventsProcessed { get; init; } /// /// Optional error information if the projection is in a failed state. /// public string? LastError { get; init; } /// /// The timestamp when the last error occurred. /// public DateTimeOffset? LastErrorAt { get; init; } /// /// Creates a new checkpoint with updated offset and timestamp. /// public ProjectionCheckpoint WithOffset(long offset) { return this with { LastProcessedOffset = offset, LastUpdated = DateTimeOffset.UtcNow, EventsProcessed = EventsProcessed + 1, LastError = null, LastErrorAt = null }; } /// /// Creates a new checkpoint with error information. /// public ProjectionCheckpoint WithError(string error) { return this with { LastError = error, LastErrorAt = DateTimeOffset.UtcNow }; } }