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

181 lines
5.6 KiB
C#

using System;
using System.Threading;
using System.Threading.Tasks;
namespace Svrnty.CQRS.Events.Abstractions.Projections;
/// <summary>
/// Manages the lifecycle and execution of event stream projections.
/// </summary>
/// <remarks>
/// <para>
/// <strong>Phase 7 Feature - Projection Engine:</strong>
/// The projection engine subscribes to event streams and dispatches events to registered
/// projections. It handles checkpointing, error recovery, and projection rebuilding.
/// </para>
/// <para>
/// <strong>Execution Model:</strong>
/// - Projections run continuously in background tasks
/// - Each projection maintains its own checkpoint independently
/// - Failed events are retried with exponential backoff
/// - Projections can be stopped, started, or rebuilt dynamically
/// </para>
/// </remarks>
public interface IProjectionEngine
{
/// <summary>
/// Starts a projection, consuming events from the specified stream.
/// </summary>
/// <param name="projectionName">The unique name of the projection.</param>
/// <param name="streamName">The name of the stream to consume from.</param>
/// <param name="cancellationToken">Cancellation token to stop the projection.</param>
/// <returns>A task that completes when the projection stops or fails.</returns>
/// <remarks>
/// The projection will start from its last checkpoint (or offset 0 if new).
/// This method runs continuously until cancelled or an unrecoverable error occurs.
/// </remarks>
Task RunAsync(
string projectionName,
string streamName,
CancellationToken cancellationToken = default);
/// <summary>
/// Rebuilds a projection by resetting it and replaying all events.
/// </summary>
/// <param name="projectionName">The unique name of the projection.</param>
/// <param name="streamName">The name of the stream to replay.</param>
/// <param name="cancellationToken">Cancellation token.</param>
/// <returns>A task representing the rebuild operation.</returns>
/// <remarks>
/// <para>
/// This will:
/// 1. Stop the projection if running
/// 2. Call ResetAsync() if projection implements IResettableProjection
/// 3. Reset the checkpoint to offset 0
/// 4. Replay all events from the beginning
/// 5. Resume normal operation
/// </para>
/// <para>
/// Use this to fix bugs in projection logic or migrate schema changes.
/// </para>
/// </remarks>
Task RebuildAsync(
string projectionName,
string streamName,
CancellationToken cancellationToken = default);
/// <summary>
/// Gets the current status of a projection.
/// </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 projection status including checkpoint and health information.</returns>
Task<ProjectionStatus> GetStatusAsync(
string projectionName,
string streamName,
CancellationToken cancellationToken = default);
}
/// <summary>
/// Represents the current status of a projection.
/// </summary>
public sealed record ProjectionStatus
{
/// <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>
/// Whether the projection is currently running.
/// </summary>
public bool IsRunning { get; init; }
/// <summary>
/// The current state of the projection.
/// </summary>
public ProjectionState State { get; init; }
/// <summary>
/// The last processed offset.
/// </summary>
public long LastProcessedOffset { get; init; }
/// <summary>
/// The current stream length (head position).
/// </summary>
public long StreamLength { get; init; }
/// <summary>
/// The number of events the projection is behind the stream head.
/// </summary>
public long Lag => StreamLength - LastProcessedOffset;
/// <summary>
/// Whether the projection is caught up with the stream.
/// </summary>
public bool IsCaughtUp => Lag <= 0;
/// <summary>
/// The timestamp when the checkpoint was last updated.
/// </summary>
public DateTimeOffset LastUpdated { get; init; }
/// <summary>
/// Total number of events processed by this projection.
/// </summary>
public long EventsProcessed { get; init; }
/// <summary>
/// The last error message if the projection failed.
/// </summary>
public string? LastError { get; init; }
/// <summary>
/// When the last error occurred.
/// </summary>
public DateTimeOffset? LastErrorAt { get; init; }
}
/// <summary>
/// Represents the execution state of a projection.
/// </summary>
public enum ProjectionState
{
/// <summary>
/// Projection has never been started.
/// </summary>
NotStarted = 0,
/// <summary>
/// Projection is actively processing events.
/// </summary>
Running = 1,
/// <summary>
/// Projection is caught up and waiting for new events.
/// </summary>
CaughtUp = 2,
/// <summary>
/// Projection has been manually stopped.
/// </summary>
Stopped = 3,
/// <summary>
/// Projection is being rebuilt (reset + replay).
/// </summary>
Rebuilding = 4,
/// <summary>
/// Projection failed with an unrecoverable error.
/// </summary>
Failed = 5
}