diff --git a/AGENTS.md b/AGENTS.md index c8f39cb..449d5eb 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -330,7 +330,7 @@ return Accepted(); // HTTP 202 - command queued for processing arguments: null); } - public async Task EnqueueAsync(OutgoingEmail email, CancellationToken cancellationToken) + public async Task EnqueueAsync(SendingEmail email, CancellationToken cancellationToken) { var json = JsonSerializer.Serialize(email); var body = Encoding.UTF8.GetBytes(json); @@ -347,7 +347,7 @@ return Accepted(); // HTTP 202 - command queued for processing await Task.CompletedTask; } - public async Task DequeueAsync(CancellationToken cancellationToken) + public async Task DequeueAsync(CancellationToken cancellationToken) { var result = _channel.BasicGet(QueueName, autoAck: false); @@ -355,7 +355,7 @@ return Accepted(); // HTTP 202 - command queued for processing return null; var json = Encoding.UTF8.GetString(result.Body.ToArray()); - var email = JsonSerializer.Deserialize(json); + var email = JsonSerializer.Deserialize(json); _channel.BasicAck(result.DeliveryTag, false); diff --git a/src/core/DigitalData.MessagingService.Abstraction/IOutgoingEmailPublisher.cs b/src/core/DigitalData.MessagingService.Abstraction/IOutgoingEmailPublisher.cs index 5924446..1270f41 100644 --- a/src/core/DigitalData.MessagingService.Abstraction/IOutgoingEmailPublisher.cs +++ b/src/core/DigitalData.MessagingService.Abstraction/IOutgoingEmailPublisher.cs @@ -3,9 +3,9 @@ namespace DigitalData.MessagingService.Abstraction; /// /// Email queue interface for outgoing emails. /// -public interface IOutgoingEmailPublisher +public interface ISendingEmailPublisher { - Task EnqueueAsync(OutgoingEmailEvent outgoingEmailEvent, CancellationToken cancellationToken = default); + Task EnqueueAsync(SendingEmailEvent sendingEmailEvent, CancellationToken cancellationToken = default); Task GetQueueDepthAsync(CancellationToken cancellationToken = default); } diff --git a/src/core/DigitalData.MessagingService.Abstraction/OutgoingEmailCreateDto.cs b/src/core/DigitalData.MessagingService.Abstraction/OutgoingEmailCreateDto.cs index 4f579d4..12c5191 100644 --- a/src/core/DigitalData.MessagingService.Abstraction/OutgoingEmailCreateDto.cs +++ b/src/core/DigitalData.MessagingService.Abstraction/OutgoingEmailCreateDto.cs @@ -1,6 +1,6 @@ namespace DigitalData.MessagingService.Abstraction; -public record OutgoingEmailCreateDto +public record SendingEmailCreateDto { /// /// Recipient email address diff --git a/src/core/DigitalData.MessagingService.Abstraction/OutgoingEmailEvent.cs b/src/core/DigitalData.MessagingService.Abstraction/OutgoingEmailEvent.cs index 344aeab..76af7eb 100644 --- a/src/core/DigitalData.MessagingService.Abstraction/OutgoingEmailEvent.cs +++ b/src/core/DigitalData.MessagingService.Abstraction/OutgoingEmailEvent.cs @@ -1,6 +1,6 @@ namespace DigitalData.MessagingService.Abstraction; -public record OutgoingEmailEvent +public record SendingEmailEvent { public Guid Id { get; set; } diff --git a/src/core/DigitalData.MessagingService.Application/Common/Mappings/EmailMappingProfile.cs b/src/core/DigitalData.MessagingService.Application/Common/Mappings/EmailMappingProfile.cs index 70176a5..b3bf7de 100644 --- a/src/core/DigitalData.MessagingService.Application/Common/Mappings/EmailMappingProfile.cs +++ b/src/core/DigitalData.MessagingService.Application/Common/Mappings/EmailMappingProfile.cs @@ -11,9 +11,9 @@ public class EmailMappingProfile : Profile { public EmailMappingProfile() { - // SendEmailCommand -> OutgoingEmailEvent + // SendEmailCommand -> SendingEmailEvent // Sender is resolved via MediatR in the handler and set separately after mapping. - CreateMap() + CreateMap() .ForMember(dest => dest.Id, opt => opt.MapFrom(_ => Guid.NewGuid())) .ForMember(dest => dest.QueuedAt, opt => opt.MapFrom(_ => DateTime.Now)) .ForMember(dest => dest.Sender, opt => opt.Ignore()); diff --git a/src/core/DigitalData.MessagingService.Application/EmailSending/Commands/SendEmailCommand.cs b/src/core/DigitalData.MessagingService.Application/EmailSending/Commands/SendEmailCommand.cs index e153bbe..240a891 100644 --- a/src/core/DigitalData.MessagingService.Application/EmailSending/Commands/SendEmailCommand.cs +++ b/src/core/DigitalData.MessagingService.Application/EmailSending/Commands/SendEmailCommand.cs @@ -9,7 +9,7 @@ namespace DigitalData.MessagingService.Application.EmailSending.Commands; /// /// Command to send an email (enqueue to RabbitMQ) /// -public record SendEmailCommand : IRequest +public record SendEmailCommand : IRequest { public required GetSenderQuery Sender { get; init; } @@ -36,23 +36,23 @@ public record SendEmailCommand : IRequest /// /// Handler for SendEmailCommand -/// Resolves the sender account via MediatR, maps to OutgoingEmailEvent and enqueues to RabbitMQ +/// Resolves the sender account via MediatR, maps to SendingEmailEvent and enqueues to RabbitMQ /// public class SendEmailCommandHandler( ISender Sender, -IOutgoingEmailPublisher Publisher, -IMapper Mapper) : IRequestHandler +ISendingEmailPublisher Publisher, +IMapper Mapper) : IRequestHandler { - public async Task Handle(SendEmailCommand request, CancellationToken cancellationToken) + public async Task Handle(SendEmailCommand request, CancellationToken cancellationToken) { var senderAccount = await Sender.Send(request.Sender, cancellationToken) ?? throw new NotFoundException( $"No email account found for the given sender criteria (Id: {request.Sender.Id}, Username: {request.Sender.Username})."); - var outgoingEmailEvent = Mapper.Map(request) with { Sender = senderAccount }; + var sendingEmailEvent = Mapper.Map(request) with { Sender = senderAccount }; // Enqueue to RabbitMQ - await Publisher.EnqueueAsync(outgoingEmailEvent, cancellationToken); - return outgoingEmailEvent; + await Publisher.EnqueueAsync(sendingEmailEvent, cancellationToken); + return sendingEmailEvent; } } diff --git a/src/infrastructure/DigitalData.MessagingService.Infrastructure/DependencyInjection.cs b/src/infrastructure/DigitalData.MessagingService.Infrastructure/DependencyInjection.cs index 355fb17..acb1122 100644 --- a/src/infrastructure/DigitalData.MessagingService.Infrastructure/DependencyInjection.cs +++ b/src/infrastructure/DigitalData.MessagingService.Infrastructure/DependencyInjection.cs @@ -33,7 +33,7 @@ public static class DependencyInjection services.AddSingleton(); // --- Email Queue (RabbitMQ) --- - services.AddSingleton(); + services.AddSingleton(); services.AddMessagingServicePublisher(); // --- RabbitMQ Configuration --- diff --git a/src/infrastructure/DigitalData.MessagingService.Infrastructure/Queue/OutgoingEmailConsumer.cs b/src/infrastructure/DigitalData.MessagingService.Infrastructure/Queue/OutgoingEmailConsumer.cs index a042c96..0bf9cd1 100644 --- a/src/infrastructure/DigitalData.MessagingService.Infrastructure/Queue/OutgoingEmailConsumer.cs +++ b/src/infrastructure/DigitalData.MessagingService.Infrastructure/Queue/OutgoingEmailConsumer.cs @@ -15,7 +15,7 @@ namespace DigitalData.MessagingService.Infrastructure.Queue; /// Provides message persistence, scalability, and reliability. /// Uses Lazy initialization pattern to avoid blocking constructor. /// -public sealed class OutgoingEmailConsumer : IAsyncDisposable +public sealed class SendingEmailConsumer : IAsyncDisposable { private readonly RabbitMqConfiguration _config; @@ -23,9 +23,9 @@ public sealed class OutgoingEmailConsumer : IAsyncDisposable private readonly Lazy _lazyInit; - private readonly ILogger? _logger; + private readonly ILogger? _logger; - public OutgoingEmailConsumer(IOptions config, IEmailService EmailService, RabbitMqConnectionFactory CnnFactory, ILogger? logger = null) + public SendingEmailConsumer(IOptions config, IEmailService EmailService, RabbitMqConnectionFactory CnnFactory, ILogger? logger = null) { _logger = logger; _config = config.Value; @@ -38,11 +38,11 @@ public sealed class OutgoingEmailConsumer : IAsyncDisposable consumer.ReceivedAsync += async (sender, args) => { - OutgoingEmailEvent? oMailEvent = null; + SendingEmailEvent? oMailEvent = null; try { var json = Encoding.UTF8.GetString(args.Body.ToArray()); - oMailEvent = JsonSerializer.Deserialize(json); + oMailEvent = JsonSerializer.Deserialize(json); if (oMailEvent is not null) { @@ -69,7 +69,7 @@ public sealed class OutgoingEmailConsumer : IAsyncDisposable // TODO: Error Reporting Strategy // Option 1: Separate RabbitMQ Queue (emailprofiler.errors) - // - Create EmailErrorReport entity { OutgoingEmailEventId, Exception, StackTrace, Timestamp, RetryAttempt } + // - Create EmailErrorReport entity { SendingEmailEventId, Exception, StackTrace, Timestamp, RetryAttempt } // - Publish to error queue: await _errorQueue.EnqueueAsync(errorReport) // - Separate worker processes error queue → Log to DB/File/External monitoring // @@ -111,7 +111,7 @@ public sealed class OutgoingEmailConsumer : IAsyncDisposable public async Task InitAsync() { if (_lazyInit.IsValueCreated) - _logger?.LogWarning("OutgoingEmailConsumer already initialized. InitAsync() called multiple times."); + _logger?.LogWarning("SendingEmailConsumer already initialized. InitAsync() called multiple times."); await _lazyInit.Value; } diff --git a/src/infrastructure/DigitalData.MessagingService.Infrastructure/Services/Background/AsyncInitWorker.cs b/src/infrastructure/DigitalData.MessagingService.Infrastructure/Services/Background/AsyncInitWorker.cs index de569c1..05f2bd0 100644 --- a/src/infrastructure/DigitalData.MessagingService.Infrastructure/Services/Background/AsyncInitWorker.cs +++ b/src/infrastructure/DigitalData.MessagingService.Infrastructure/Services/Background/AsyncInitWorker.cs @@ -9,7 +9,7 @@ namespace DigitalData.MessagingService.Infrastructure.Services.Background; /// Leverages a push-based, event-driven RabbitMQ consumer to eliminate polling overhead. /// Email account configuration is resolved exclusively from application settings; no database access is performed. /// -public class AsyncInitWorker(OutgoingEmailConsumer EmailConsumer) : BackgroundService +public class AsyncInitWorker(SendingEmailConsumer EmailConsumer) : BackgroundService { protected override async Task ExecuteAsync(CancellationToken stoppingToken) { diff --git a/src/infrastructure/DigitalData.MessagingService.Publisher/DependencyInjection.cs b/src/infrastructure/DigitalData.MessagingService.Publisher/DependencyInjection.cs index 3b75b88..214c2a3 100644 --- a/src/infrastructure/DigitalData.MessagingService.Publisher/DependencyInjection.cs +++ b/src/infrastructure/DigitalData.MessagingService.Publisher/DependencyInjection.cs @@ -13,7 +13,7 @@ public static class DependencyInjection services.AddRabbitMqConnectionFactory(configure); // --- Email Queue (RabbitMQ) --- - services.AddSingleton(); + services.AddSingleton(); return services; } diff --git a/src/infrastructure/DigitalData.MessagingService.Publisher/OutgoingEmailPublisher.cs b/src/infrastructure/DigitalData.MessagingService.Publisher/OutgoingEmailPublisher.cs index 2be2a89..b6ca4b1 100644 --- a/src/infrastructure/DigitalData.MessagingService.Publisher/OutgoingEmailPublisher.cs +++ b/src/infrastructure/DigitalData.MessagingService.Publisher/OutgoingEmailPublisher.cs @@ -13,14 +13,14 @@ namespace DigitalData.MessagingService.Publisher; /// Provides message persistence, scalability, and reliability. /// Uses Lazy initialization pattern to avoid blocking constructor. /// -public sealed class OutgoingEmailPublisher : IOutgoingEmailPublisher, IAsyncDisposable +public sealed class SendingEmailPublisher : ISendingEmailPublisher, IAsyncDisposable { private readonly RabbitMqConfiguration _config; - private readonly ILogger _logger; + private readonly ILogger _logger; private readonly RabbitMqConnectionFactory _cnnFactory; private readonly Lazy> _lazyChannel; - public OutgoingEmailPublisher(IOptions config, ILogger logger, RabbitMqConnectionFactory cnnFactory) + public SendingEmailPublisher(IOptions config, ILogger logger, RabbitMqConnectionFactory cnnFactory) { _config = config.Value; _logger = logger; @@ -66,9 +66,9 @@ public sealed class OutgoingEmailPublisher : IOutgoingEmailPublisher, IAsyncDisp return channel; } - public async Task EnqueueAsync(OutgoingEmailEvent outgoingEmailEvent, CancellationToken cancellationToken = default) + public async Task EnqueueAsync(SendingEmailEvent sendingEmailEvent, CancellationToken cancellationToken = default) { - var json = JsonSerializer.Serialize(outgoingEmailEvent); + var json = JsonSerializer.Serialize(sendingEmailEvent); var body = Encoding.UTF8.GetBytes(json); var properties = new BasicProperties diff --git a/src/presentation/DigitalData.MessagingService.API/Controllers/EmailsController.cs b/src/presentation/DigitalData.MessagingService.API/Controllers/EmailsController.cs index 387cecc..fde87cd 100644 --- a/src/presentation/DigitalData.MessagingService.API/Controllers/EmailsController.cs +++ b/src/presentation/DigitalData.MessagingService.API/Controllers/EmailsController.cs @@ -23,11 +23,11 @@ public class EmailsController(IMediator mediator) : ControllerBase [ProducesResponseType(StatusCodes.Status400BadRequest)] public async Task SendEmail([FromBody] SendEmailCommand command, CancellationToken cancellationToken) { - var outgoingEmailEvent = await mediator.Send(command, cancellationToken); + var sendingEmailEvent = await mediator.Send(command, cancellationToken); return Accepted(new { - outgoingEmailEvent.Id, + sendingEmailEvent.Id, }); } } diff --git a/src/presentation/DigitalData.MessagingService.Client/Email.cs b/src/presentation/DigitalData.MessagingService.Client/Email.cs index 85e46c2..bfd6f2b 100644 --- a/src/presentation/DigitalData.MessagingService.Client/Email.cs +++ b/src/presentation/DigitalData.MessagingService.Client/Email.cs @@ -28,12 +28,12 @@ public record Email public bool IsHtml { get; set; } = true; /// - /// Converts this instance to an . + /// Converts this instance to an . /// - /// A new representing this email. - internal OutgoingEmailEvent ToEvent() + /// A new representing this email. + internal SendingEmailEvent ToEvent() { - return new OutgoingEmailEvent + return new SendingEmailEvent { Id = Guid.NewGuid(), Recipients = Recipients, diff --git a/src/presentation/DigitalData.MessagingService.Client/EmailSender.cs b/src/presentation/DigitalData.MessagingService.Client/EmailSender.cs index 9f85111..81a3502 100644 --- a/src/presentation/DigitalData.MessagingService.Client/EmailSender.cs +++ b/src/presentation/DigitalData.MessagingService.Client/EmailSender.cs @@ -113,8 +113,8 @@ public static class EmailSender /// Thrown when has not been called prior to sending. /// /// - /// This method maps to , - /// then resolves from the internal + /// This method maps to , + /// then resolves from the internal /// service provider and calls EnqueueAsync in a fire-and-forget manner. /// Ensure that any unhandled exceptions from the async operation are handled /// at the publisher level. @@ -124,7 +124,7 @@ public static class EmailSender if(!IsConnected) throw new InvalidOperationException("Messaging service is not connected. Call ConnectRabbitMq first."); - var publisher = LazyProvider.Value.GetRequiredService(); + var publisher = LazyProvider.Value.GetRequiredService(); publisher.EnqueueAsync(email.ToEvent()); } } \ No newline at end of file diff --git a/tests/DigitalData.MessagingService.Tests/Integration/OutgoingEmailPublisherTests.cs b/tests/DigitalData.MessagingService.Tests/Integration/OutgoingEmailPublisherTests.cs index 855ea68..b4fd0d7 100644 --- a/tests/DigitalData.MessagingService.Tests/Integration/OutgoingEmailPublisherTests.cs +++ b/tests/DigitalData.MessagingService.Tests/Integration/OutgoingEmailPublisherTests.cs @@ -10,31 +10,31 @@ using RabbitMQ.Client; namespace DigitalData.MessagingService.Tests.Integration; /// -/// Integration tests for against the real RabbitMQ broker. +/// Integration tests for against the real RabbitMQ broker. /// Each test publishes a message and immediately reads it back via BasicGetAsync to verify /// the full round-trip without starting the consumer (which requires Limilabs Mail.dll). /// -public sealed class OutgoingEmailPublisherTests : IAsyncDisposable +public sealed class SendingEmailPublisherTests : IAsyncDisposable { private readonly ServiceProvider _serviceProvider; - private readonly IOutgoingEmailPublisher _publisher; + private readonly ISendingEmailPublisher _publisher; private readonly RabbitMqConnectionFactory _factory; - public OutgoingEmailPublisherTests() + public SendingEmailPublisherTests() { var services = new ServiceCollection(); services.AddLogging(); services.AddMessagingServicePublisher(RabbitMqTestConfig.Apply); _serviceProvider = services.BuildServiceProvider(); - _publisher = _serviceProvider.GetRequiredService(); + _publisher = _serviceProvider.GetRequiredService(); _factory = _serviceProvider.GetRequiredService(); } [Fact] public async Task EnqueueAsync_PublishesMessage_MessageArrivesInQueue() { - var email = new OutgoingEmailEvent + var email = new SendingEmailEvent { Id = Guid.NewGuid(), Recipients = ["test@example.com"], @@ -59,7 +59,7 @@ public sealed class OutgoingEmailPublisherTests : IAsyncDisposable [Fact] public async Task EnqueueAsync_MultipleMessages_AllArrivesInQueue() { - var emails = Enumerable.Range(1, 3).Select(i => new OutgoingEmailEvent + var emails = Enumerable.Range(1, 3).Select(i => new SendingEmailEvent { Id = Guid.NewGuid(), Recipients = new List { $"recipient{i}@example.com" }, @@ -82,7 +82,7 @@ public sealed class OutgoingEmailPublisherTests : IAsyncDisposable [Fact] public async Task GetQueueDepthAsync_AfterPublish_ReturnsPositiveDepth() { - var email = new OutgoingEmailEvent + var email = new SendingEmailEvent { Id = Guid.NewGuid(), Recipients = ["depth-test@example.com"], @@ -105,7 +105,7 @@ public sealed class OutgoingEmailPublisherTests : IAsyncDisposable { var id = Guid.NewGuid(); - var email = new OutgoingEmailEvent + var email = new SendingEmailEvent { Id = id, Recipients = new List { "serialize@example.com" }, @@ -132,7 +132,7 @@ public sealed class OutgoingEmailPublisherTests : IAsyncDisposable /// /// Reads a single message from the queue without acknowledging it (peek via nack+requeue). /// - private async Task PeekMessageAsync() + private async Task PeekMessageAsync() { var connection = await _factory.GetDefaultConnectionAsync(); await using var channel = await connection.CreateChannelAsync(); @@ -146,21 +146,21 @@ public sealed class OutgoingEmailPublisherTests : IAsyncDisposable await channel.BasicNackAsync(result.DeliveryTag, multiple: false, requeue: true); var json = Encoding.UTF8.GetString(result.Body.ToArray()); - return JsonSerializer.Deserialize(json); + return JsonSerializer.Deserialize(json); } /// /// Scans queue messages (up to a limit) to find a message matching the given . /// All messages are re-queued after inspection. /// - private async Task FindMessageAsync(Guid id, int maxMessages = 50) + private async Task FindMessageAsync(Guid id, int maxMessages = 50) { var connection = await _factory.GetDefaultConnectionAsync(); await using var channel = await connection.CreateChannelAsync(); var requeue = new List<(ulong DeliveryTag, byte[] Body)>(); - OutgoingEmailEvent? found = null; + SendingEmailEvent? found = null; for (int i = 0; i < maxMessages; i++) { @@ -171,7 +171,7 @@ public sealed class OutgoingEmailPublisherTests : IAsyncDisposable requeue.Add((result.DeliveryTag, result.Body.ToArray())); var json = Encoding.UTF8.GetString(result.Body.ToArray()); - var evt = JsonSerializer.Deserialize(json); + var evt = JsonSerializer.Deserialize(json); if (evt?.Id == id) {