dotnet-cqrs/Svrnty.CQRS.Events.Abstractions/Configuration/ExternalDeliveryConfiguration.cs

150 lines
5.7 KiB
C#

using System;
using System.Collections.Generic;
namespace Svrnty.CQRS.Events.Abstractions.Configuration;
/// <summary>
/// Configuration for external event delivery to cross-service message brokers.
/// </summary>
/// <remarks>
/// This configuration is used to specify how events from a stream should be
/// published externally to other services via message brokers like RabbitMQ or Kafka.
/// </remarks>
public sealed class ExternalDeliveryConfiguration
{
/// <summary>
/// Gets or sets whether external delivery is enabled for this stream.
/// </summary>
/// <remarks>
/// Default: false (events remain internal to the service).
/// </remarks>
public bool Enabled { get; set; } = false;
/// <summary>
/// Gets or sets the provider type to use for external delivery.
/// </summary>
/// <remarks>
/// Supported values: "RabbitMQ", "Kafka", "AzureServiceBus", "AwsSns"
/// Default: null (must be specified if Enabled = true)
/// </remarks>
public string? ProviderType { get; set; }
/// <summary>
/// Gets or sets the connection string for the external message broker.
/// </summary>
/// <remarks>
/// <para><strong>RabbitMQ:</strong> amqp://user:pass@localhost:5672/vhost</para>
/// <para><strong>Kafka:</strong> localhost:9092</para>
/// <para><strong>Azure Service Bus:</strong> Endpoint=sb://...;SharedAccessKey=...</para>
/// </remarks>
public string? ConnectionString { get; set; }
/// <summary>
/// Gets or sets the exchange name (RabbitMQ) or topic name (Kafka).
/// </summary>
/// <remarks>
/// If not specified, defaults to the stream name.
/// Example: "user-service.events" or "orders.events"
/// </remarks>
public string? ExchangeName { get; set; }
/// <summary>
/// Gets or sets the exchange type for RabbitMQ.
/// </summary>
/// <remarks>
/// Supported values: "topic", "fanout", "direct", "headers"
/// Default: "topic" (recommended for most scenarios)
/// </remarks>
public string ExchangeType { get; set; } = "topic";
/// <summary>
/// Gets or sets the routing key strategy for RabbitMQ.
/// </summary>
/// <remarks>
/// <para>Supported strategies:</para>
/// <list type="bullet">
/// <item><term>EventType</term><description>Route by event type name (e.g., "UserCreatedEvent")</description></item>
/// <item><term>StreamName</term><description>Route by stream name (e.g., "user-events")</description></item>
/// <item><term>Custom</term><description>Use custom routing key from metadata</description></item>
/// <item><term>Wildcard</term><description>Route to all consumers (use "*" routing key)</description></item>
/// </list>
/// Default: "EventType"
/// </remarks>
public string RoutingKeyStrategy { get; set; } = "EventType";
/// <summary>
/// Gets or sets whether to automatically declare/create the exchange and queues.
/// </summary>
/// <remarks>
/// Default: true (recommended for development).
/// Set to false in production if topology is managed externally.
/// </remarks>
public bool AutoDeclareTopology { get; set; } = true;
/// <summary>
/// Gets or sets whether messages should be persistent (survive broker restart).
/// </summary>
/// <remarks>
/// Default: true (durable messages).
/// Set to false for fire-and-forget scenarios where message loss is acceptable.
/// </remarks>
public bool Persistent { get; set; } = true;
/// <summary>
/// Gets or sets the maximum number of retry attempts for failed publishes.
/// </summary>
/// <remarks>
/// Default: 3
/// Set to 0 to disable retries.
/// </remarks>
public int MaxRetries { get; set; } = 3;
/// <summary>
/// Gets or sets the delay between retry attempts.
/// </summary>
/// <remarks>
/// Default: 1 second
/// Exponential backoff is applied (delay * 2^attemptNumber).
/// </remarks>
public TimeSpan RetryDelay { get; set; } = TimeSpan.FromSeconds(1);
/// <summary>
/// Gets or sets additional provider-specific settings.
/// </summary>
/// <remarks>
/// This allows passing custom configuration to specific providers without
/// changing the core configuration model.
/// </remarks>
public Dictionary<string, string> AdditionalSettings { get; set; } = new();
/// <summary>
/// Validates the configuration.
/// </summary>
/// <exception cref="InvalidOperationException">Thrown if the configuration is invalid.</exception>
public void Validate()
{
if (!Enabled)
return;
if (string.IsNullOrWhiteSpace(ProviderType))
throw new InvalidOperationException("ProviderType must be specified when external delivery is enabled.");
if (string.IsNullOrWhiteSpace(ConnectionString))
throw new InvalidOperationException("ConnectionString must be specified when external delivery is enabled.");
if (MaxRetries < 0)
throw new InvalidOperationException("MaxRetries cannot be negative.");
if (RetryDelay <= TimeSpan.Zero)
throw new InvalidOperationException("RetryDelay must be positive.");
var validExchangeTypes = new[] { "topic", "fanout", "direct", "headers" };
if (!validExchangeTypes.Contains(ExchangeType.ToLowerInvariant()))
throw new InvalidOperationException($"ExchangeType must be one of: {string.Join(", ", validExchangeTypes)}");
var validRoutingStrategies = new[] { "EventType", "StreamName", "Custom", "Wildcard" };
if (!validRoutingStrategies.Contains(RoutingKeyStrategy))
throw new InvalidOperationException($"RoutingKeyStrategy must be one of: {string.Join(", ", validRoutingStrategies)}");
}
}