dotnet-cqrs/Svrnty.CQRS.Events.Abstractions/Projections/IProjectionCheckpointStore.cs

158 lines
5.5 KiB
C#

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