101 lines
3.7 KiB
C#
101 lines
3.7 KiB
C#
using System;
|
|
using Svrnty.CQRS.Events.Abstractions.Subscriptions;
|
|
using System.Collections.Generic;
|
|
using Svrnty.CQRS.Events.Abstractions;
|
|
|
|
namespace Svrnty.CQRS.Events.Subscriptions;
|
|
|
|
/// <summary>
|
|
/// Default implementation of <see cref="ISubscription"/>.
|
|
/// Represents a subscription configuration for consuming events from a stream.
|
|
/// </summary>
|
|
public class Subscription : ISubscription
|
|
{
|
|
/// <summary>
|
|
/// Initializes a new instance of the <see cref="Subscription"/> class.
|
|
/// </summary>
|
|
/// <param name="subscriptionId">Unique subscription identifier.</param>
|
|
/// <param name="streamName">Name of the stream to subscribe to.</param>
|
|
/// <param name="mode">Subscription mode (Broadcast, Exclusive, etc.).</param>
|
|
public Subscription(string subscriptionId, string streamName, SubscriptionMode mode = SubscriptionMode.Broadcast)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(subscriptionId))
|
|
throw new ArgumentException("Subscription ID cannot be null or whitespace.", nameof(subscriptionId));
|
|
if (string.IsNullOrWhiteSpace(streamName))
|
|
throw new ArgumentException("Stream name cannot be null or whitespace.", nameof(streamName));
|
|
|
|
SubscriptionId = subscriptionId;
|
|
StreamName = streamName;
|
|
Mode = mode;
|
|
CreatedAt = DateTimeOffset.UtcNow;
|
|
IsActive = true;
|
|
VisibilityTimeout = TimeSpan.FromSeconds(30); // Default 30 seconds
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public string SubscriptionId { get; }
|
|
|
|
/// <inheritdoc />
|
|
public string StreamName { get; }
|
|
|
|
/// <inheritdoc />
|
|
public SubscriptionMode Mode { get; set; }
|
|
|
|
/// <inheritdoc />
|
|
public HashSet<string>? EventTypeFilter { get; set; }
|
|
|
|
/// <inheritdoc />
|
|
public bool IsActive { get; set; }
|
|
|
|
/// <inheritdoc />
|
|
public DateTimeOffset CreatedAt { get; }
|
|
|
|
/// <inheritdoc />
|
|
public string? Description { get; set; }
|
|
|
|
/// <inheritdoc />
|
|
public int? MaxConcurrentConsumers { get; set; }
|
|
|
|
/// <inheritdoc />
|
|
public TimeSpan VisibilityTimeout { get; set; }
|
|
|
|
/// <inheritdoc />
|
|
public IReadOnlyDictionary<string, string>? Metadata { get; set; }
|
|
|
|
// ========================================================================
|
|
// Phase 5: Schema Evolution Support
|
|
// ========================================================================
|
|
|
|
/// <inheritdoc />
|
|
public bool EnableUpcasting { get; set; }
|
|
|
|
/// <inheritdoc />
|
|
public int? TargetEventVersion { get; set; }
|
|
|
|
/// <summary>
|
|
/// Validates the subscription configuration.
|
|
/// </summary>
|
|
/// <exception cref="InvalidOperationException">Thrown if configuration is invalid.</exception>
|
|
public void Validate()
|
|
{
|
|
if (string.IsNullOrWhiteSpace(SubscriptionId))
|
|
throw new InvalidOperationException("Subscription ID cannot be null or whitespace.");
|
|
|
|
if (string.IsNullOrWhiteSpace(StreamName))
|
|
throw new InvalidOperationException("Stream name cannot be null or whitespace.");
|
|
|
|
if (VisibilityTimeout <= TimeSpan.Zero)
|
|
throw new InvalidOperationException($"Visibility timeout must be positive. Got: {VisibilityTimeout}");
|
|
|
|
if (MaxConcurrentConsumers.HasValue && MaxConcurrentConsumers.Value <= 0)
|
|
throw new InvalidOperationException($"MaxConcurrentConsumers must be positive if set. Got: {MaxConcurrentConsumers}");
|
|
|
|
// Validate mode-specific constraints
|
|
if (Mode == SubscriptionMode.ConsumerGroup && MaxConcurrentConsumers.HasValue && MaxConcurrentConsumers.Value == 1)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"ConsumerGroup mode with MaxConcurrentConsumers=1 is inefficient. Use Exclusive mode instead.");
|
|
}
|
|
}
|
|
}
|