156 lines
6.1 KiB
C#
156 lines
6.1 KiB
C#
using System;
|
|
using Svrnty.CQRS.Events.Abstractions.Subscriptions;
|
|
using System.Collections.Generic;
|
|
using System.Threading;
|
|
using System.Threading.Tasks;
|
|
|
|
namespace Svrnty.CQRS.Events.Abstractions.Subscriptions;
|
|
|
|
/// <summary>
|
|
/// Registry for tracking active consumers subscribed to event streams.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// <para>
|
|
/// The consumer registry tracks which consumers are actively listening to which subscriptions.
|
|
/// This is different from <see cref="ISubscriptionStore"/> which tracks subscription configurations.
|
|
/// </para>
|
|
/// <para>
|
|
/// <strong>Usage:</strong>
|
|
/// - A subscription defines WHAT to listen to (e.g., "user-events with filter X")
|
|
/// - A consumer is WHO is listening (e.g., "analytics-service-instance-1")
|
|
/// - Multiple consumers can listen to the same subscription (broadcast or consumer group)
|
|
/// </para>
|
|
/// </remarks>
|
|
public interface IConsumerRegistry
|
|
{
|
|
/// <summary>
|
|
/// Register a consumer for a subscription.
|
|
/// </summary>
|
|
/// <param name="subscriptionId">The subscription ID.</param>
|
|
/// <param name="consumerId">The consumer ID.</param>
|
|
/// <param name="metadata">Optional metadata about the consumer (e.g., hostname, version).</param>
|
|
/// <param name="cancellationToken">Cancellation token.</param>
|
|
/// <returns>A task representing the async operation.</returns>
|
|
/// <remarks>
|
|
/// Registers the consumer as actively listening to the subscription.
|
|
/// If the consumer is already registered, updates the last heartbeat timestamp.
|
|
/// </remarks>
|
|
Task RegisterConsumerAsync(
|
|
string subscriptionId,
|
|
string consumerId,
|
|
Dictionary<string, string>? metadata = null,
|
|
CancellationToken cancellationToken = default);
|
|
|
|
/// <summary>
|
|
/// Unregister a consumer from a subscription.
|
|
/// </summary>
|
|
/// <param name="subscriptionId">The subscription ID.</param>
|
|
/// <param name="consumerId">The consumer ID.</param>
|
|
/// <param name="cancellationToken">Cancellation token.</param>
|
|
/// <returns>True if the consumer was unregistered, false if not found.</returns>
|
|
/// <remarks>
|
|
/// Removes the consumer from the active consumer list.
|
|
/// Should be called when a consumer disconnects or stops listening.
|
|
/// </remarks>
|
|
Task<bool> UnregisterConsumerAsync(
|
|
string subscriptionId,
|
|
string consumerId,
|
|
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 IDs.</returns>
|
|
/// <remarks>
|
|
/// Returns consumers that are currently registered and have recent heartbeats.
|
|
/// Stale consumers (no heartbeat for timeout period) are automatically excluded.
|
|
/// </remarks>
|
|
Task<List<string>> GetConsumersAsync(
|
|
string subscriptionId,
|
|
CancellationToken cancellationToken = default);
|
|
|
|
/// <summary>
|
|
/// Get detailed information about all active consumers for a subscription.
|
|
/// </summary>
|
|
/// <param name="subscriptionId">The subscription ID.</param>
|
|
/// <param name="cancellationToken">Cancellation token.</param>
|
|
/// <returns>List of consumer information including metadata and timestamps.</returns>
|
|
Task<List<ConsumerInfo>> GetConsumerInfoAsync(
|
|
string subscriptionId,
|
|
CancellationToken cancellationToken = default);
|
|
|
|
/// <summary>
|
|
/// Update the heartbeat timestamp for a consumer.
|
|
/// </summary>
|
|
/// <param name="subscriptionId">The subscription ID.</param>
|
|
/// <param name="consumerId">The consumer ID.</param>
|
|
/// <param name="cancellationToken">Cancellation token.</param>
|
|
/// <returns>True if the heartbeat was updated, false if consumer not found.</returns>
|
|
/// <remarks>
|
|
/// Consumers should send heartbeats periodically to indicate they're still active.
|
|
/// Consumers without recent heartbeats are considered stale and automatically removed.
|
|
/// </remarks>
|
|
Task<bool> HeartbeatAsync(
|
|
string subscriptionId,
|
|
string consumerId,
|
|
CancellationToken cancellationToken = default);
|
|
|
|
/// <summary>
|
|
/// Check if a specific consumer is currently registered.
|
|
/// </summary>
|
|
/// <param name="subscriptionId">The subscription ID.</param>
|
|
/// <param name="consumerId">The consumer ID.</param>
|
|
/// <param name="cancellationToken">Cancellation token.</param>
|
|
/// <returns>True if the consumer is active, false otherwise.</returns>
|
|
Task<bool> IsConsumerActiveAsync(
|
|
string subscriptionId,
|
|
string consumerId,
|
|
CancellationToken cancellationToken = default);
|
|
|
|
/// <summary>
|
|
/// Remove stale consumers that haven't sent heartbeats within the timeout period.
|
|
/// </summary>
|
|
/// <param name="timeout">Consider consumers stale if no heartbeat for this duration.</param>
|
|
/// <param name="cancellationToken">Cancellation token.</param>
|
|
/// <returns>Number of stale consumers removed.</returns>
|
|
/// <remarks>
|
|
/// This should be called periodically by a background service to clean up disconnected consumers.
|
|
/// </remarks>
|
|
Task<int> RemoveStaleConsumersAsync(
|
|
TimeSpan timeout,
|
|
CancellationToken cancellationToken = default);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Information about a registered consumer.
|
|
/// </summary>
|
|
public sealed record ConsumerInfo
|
|
{
|
|
/// <summary>
|
|
/// The consumer ID.
|
|
/// </summary>
|
|
public required string ConsumerId { get; init; }
|
|
|
|
/// <summary>
|
|
/// The subscription ID this consumer is subscribed to.
|
|
/// </summary>
|
|
public required string SubscriptionId { get; init; }
|
|
|
|
/// <summary>
|
|
/// When the consumer was first registered.
|
|
/// </summary>
|
|
public required DateTimeOffset RegisteredAt { get; init; }
|
|
|
|
/// <summary>
|
|
/// When the consumer last sent a heartbeat.
|
|
/// </summary>
|
|
public required DateTimeOffset LastHeartbeat { get; init; }
|
|
|
|
/// <summary>
|
|
/// Optional metadata about the consumer (e.g., hostname, version, process ID).
|
|
/// </summary>
|
|
public IReadOnlyDictionary<string, string>? Metadata { get; init; }
|
|
}
|