using DigitalData.MessagingService.Application.Common.Interfaces; using DigitalData.MessagingService.RabbitMQ; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; namespace DigitalData.MessagingService.Infrastructure.Queue; /// /// Manages a pool of instances that compete for messages /// on the same RabbitMQ queue (competing consumers pattern). /// Each consumer owns a dedicated channel, so they process messages fully in parallel /// without any shared locking or synchronization primitives. /// public sealed class SendingEmailConsumerPool : IAsyncDisposable { private readonly List _consumers; private readonly ILogger? _logger; private readonly int _concurrency; public SendingEmailConsumerPool( IOptions config, IEmailService emailService, RabbitMqConnectionFactory cnnFactory, ILogger? logger = null, ILogger? consumerLogger = null) { _logger = logger; _concurrency = config.Value.ConsumerConcurrency; _consumers = [.. Enumerable .Range(0, _concurrency) .Select(_ => new SendingEmailConsumer(config.Value.QueueName, emailService, cnnFactory, consumerLogger))]; } /// /// Starts all consumers in parallel. Each consumer opens its own channel and begins listening. /// public async Task InitAsync() { _logger?.LogInformation("Starting {Count} competing email consumers.", _concurrency); await Task.WhenAll(_consumers.Select(c => c.InitAsync())); _logger?.LogInformation("All {Count} email consumers started.", _concurrency); } public async ValueTask DisposeAsync() { await Task.WhenAll(_consumers.Select(async c => await c.DisposeAsync().AsTask())); } }