158 lines
5.5 KiB
C#
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
|
|
};
|
|
}
|
|
}
|