150 lines
5.7 KiB
C#
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)}");
|
|
}
|
|
}
|