181 lines
5.6 KiB
C#
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
|
|
}
|