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