dotnet-cqrs/Svrnty.CQRS.Events.Abstractions/Subscriptions/IEventSubscriptionClient.cs

200 lines
8.4 KiB
C#

using System;
using Svrnty.CQRS.Events.Abstractions.EventStore;
using Svrnty.CQRS.Events.Abstractions.Models;
using Svrnty.CQRS.Events.Abstractions.Storage;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
namespace Svrnty.CQRS.Events.Abstractions.Subscriptions;
/// <summary>
/// Client interface for subscribing to event streams and consuming events.
/// </summary>
/// <remarks>
/// <para>
/// This is the primary interface consumers use to receive events from subscriptions.
/// Supports async enumeration (IAsyncEnumerable) for streaming consumption.
/// </para>
/// <para>
/// <strong>Usage Pattern:</strong>
/// <code>
/// await foreach (var @event in client.SubscribeAsync("my-subscription", "consumer-1", ct))
/// {
/// // Process event
/// await ProcessEventAsync(@event);
///
/// // Event is automatically acknowledged after successful processing
/// // (unless manual acknowledgment mode is enabled)
/// }
/// </code>
/// </para>
/// </remarks>
public interface IEventSubscriptionClient
{
/// <summary>
/// Subscribe to a subscription and receive events as an async stream.
/// </summary>
/// <param name="subscriptionId">The subscription ID to consume from.</param>
/// <param name="consumerId">Unique identifier for this consumer instance.</param>
/// <param name="cancellationToken">Cancellation token to stop consuming.</param>
/// <returns>Async enumerable stream of events.</returns>
/// <remarks>
/// <para>
/// Events are automatically acknowledged after being yielded, unless manual acknowledgment is enabled.
/// The consumer is automatically registered when enumeration starts and unregistered when it stops.
/// </para>
/// <para>
/// <strong>Subscription Modes:</strong>
/// - Broadcast: Each consumer gets all events
/// - Exclusive: Only one consumer gets each event (load balanced)
/// - ConsumerGroup: Load balanced across group members
/// - ReadReceipt: Requires explicit MarkAsRead call
/// </para>
/// </remarks>
IAsyncEnumerable<ICorrelatedEvent> SubscribeAsync(
string subscriptionId,
string consumerId,
CancellationToken cancellationToken = default);
/// <summary>
/// Subscribe with consumer metadata (hostname, version, etc.).
/// </summary>
/// <param name="subscriptionId">The subscription ID to consume from.</param>
/// <param name="consumerId">Unique identifier for this consumer instance.</param>
/// <param name="metadata">Optional metadata about this consumer.</param>
/// <param name="cancellationToken">Cancellation token to stop consuming.</param>
/// <returns>Async enumerable stream of events.</returns>
IAsyncEnumerable<ICorrelatedEvent> SubscribeAsync(
string subscriptionId,
string consumerId,
Dictionary<string, string> metadata,
CancellationToken cancellationToken = default);
/// <summary>
/// Manually acknowledge an event (only needed if manual acknowledgment mode is enabled).
/// </summary>
/// <param name="subscriptionId">The subscription ID.</param>
/// <param name="eventId">The event ID to acknowledge.</param>
/// <param name="consumerId">The consumer ID acknowledging the event.</param>
/// <param name="cancellationToken">Cancellation token.</param>
/// <returns>True if acknowledged, false if event not found or already acknowledged.</returns>
Task<bool> AcknowledgeAsync(
string subscriptionId,
string eventId,
string consumerId,
CancellationToken cancellationToken = default);
/// <summary>
/// Negative acknowledge an event (NACK), marking it for redelivery or dead letter.
/// </summary>
/// <param name="subscriptionId">The subscription ID.</param>
/// <param name="eventId">The event ID to NACK.</param>
/// <param name="consumerId">The consumer ID nacking the event.</param>
/// <param name="requeue">If true, requeue for retry. If false, move to dead letter queue.</param>
/// <param name="cancellationToken">Cancellation token.</param>
/// <returns>True if nacked, false if event not found.</returns>
Task<bool> NackAsync(
string subscriptionId,
string eventId,
string consumerId,
bool requeue = true,
CancellationToken cancellationToken = default);
/// <summary>
/// Get subscription details.
/// </summary>
/// <param name="subscriptionId">The subscription ID.</param>
/// <param name="cancellationToken">Cancellation token.</param>
/// <returns>The subscription configuration, or null if not found.</returns>
Task<ISubscription?> GetSubscriptionAsync(
string subscriptionId,
CancellationToken cancellationToken = default);
/// <summary>
/// Get all active consumers for a subscription.
/// </summary>
/// <param name="subscriptionId">The subscription ID.</param>
/// <param name="cancellationToken">Cancellation token.</param>
/// <returns>List of active consumer information.</returns>
Task<List<ConsumerInfo>> GetActiveConsumersAsync(
string subscriptionId,
CancellationToken cancellationToken = default);
/// <summary>
/// Unsubscribe a consumer from a subscription.
/// </summary>
/// <param name="subscriptionId">The subscription ID.</param>
/// <param name="consumerId">The consumer ID to unregister.</param>
/// <param name="cancellationToken">Cancellation token.</param>
/// <returns>True if unregistered, false if not found.</returns>
Task<bool> UnsubscribeAsync(
string subscriptionId,
string consumerId,
CancellationToken cancellationToken = default);
// ========================================================================
// Phase 3: Read Receipt API (Consumer Progress Tracking)
// ========================================================================
/// <summary>
/// Records a read receipt for an event, tracking consumer progress.
/// </summary>
/// <param name="streamName">The stream name.</param>
/// <param name="consumerId">The consumer identifier.</param>
/// <param name="eventId">The event ID being acknowledged.</param>
/// <param name="offset">The event's offset/position in the stream.</param>
/// <param name="cancellationToken">Cancellation token.</param>
/// <remarks>
/// <para>
/// Read receipts differ from acknowledgments:
/// - Acknowledgments are for subscription delivery tracking
/// - Read receipts are for consumer progress/offset tracking
/// </para>
/// <para>
/// Use this to track which events a consumer has successfully processed,
/// allowing resume from last position and monitoring consumer lag.
/// </para>
/// </remarks>
Task RecordReadReceiptAsync(
string streamName,
string consumerId,
string eventId,
long offset,
CancellationToken cancellationToken = default);
/// <summary>
/// Gets the last acknowledged offset for a consumer on a stream.
/// </summary>
/// <param name="streamName">The stream name.</param>
/// <param name="consumerId">The consumer identifier.</param>
/// <param name="cancellationToken">Cancellation token.</param>
/// <returns>The last acknowledged offset, or null if no receipts exist.</returns>
Task<long?> GetLastReadOffsetAsync(
string streamName,
string consumerId,
CancellationToken cancellationToken = default);
/// <summary>
/// Gets consumer progress statistics for a stream.
/// </summary>
/// <param name="streamName">The stream name.</param>
/// <param name="consumerId">The consumer identifier.</param>
/// <param name="cancellationToken">Cancellation token.</param>
/// <returns>Consumer progress information, or null if no receipts exist.</returns>
Task<ConsumerProgress?> GetConsumerProgressAsync(
string streamName,
string consumerId,
CancellationToken cancellationToken = default);
/// <summary>
/// Gets all consumers tracking a specific stream.
/// </summary>
/// <param name="streamName">The stream name.</param>
/// <param name="cancellationToken">Cancellation token.</param>
/// <returns>List of consumer IDs tracking this stream.</returns>
Task<IReadOnlyList<string>> GetStreamConsumersAsync(
string streamName,
CancellationToken cancellationToken = default);
}