feat: consume transactional email notifications
Add contract validation, SMTP delivery results, terminal failure context, neutral development seeding, and local Docker setup. Ref: IT-1033
This commit is contained in:
@@ -0,0 +1,3 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
[assembly: InternalsVisibleTo("HrynCo.NotificationService.Services.Tests")]
|
||||
@@ -1,14 +1,17 @@
|
||||
namespace HrynCo.NotificationService.Worker;
|
||||
|
||||
using HrynCo.NotificationService.Contracts.Messages;
|
||||
using Hrynco.RabbitMq;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Options;
|
||||
using HrynCo.NotificationService.Worker.Services.EmailProcessing;
|
||||
using Hrynco.RabbitMq;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
public sealed class SendEmailConsumer : RabbitMqConsumerBase<SendEmailMessage, SendEmailMessageData>
|
||||
{
|
||||
internal const string IncomingQueue = "notification.send-email";
|
||||
internal const string SupportedMessageType = "Notification.SendEmail.v1";
|
||||
|
||||
private readonly IServiceScopeFactory _scopeFactory;
|
||||
private readonly ILogger<SendEmailConsumer> _logger;
|
||||
|
||||
public SendEmailConsumer(
|
||||
IOptionsMonitor<RabbitMqSettings> options,
|
||||
@@ -17,16 +20,51 @@ public sealed class SendEmailConsumer : RabbitMqConsumerBase<SendEmailMessage, S
|
||||
: base(options, logger)
|
||||
{
|
||||
_scopeFactory = scopeFactory;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
private const string IncomingQueue = "notification.send-email";
|
||||
|
||||
protected override string QueueName => IncomingQueue;
|
||||
|
||||
protected override async Task HandleMessageAsync(SendEmailMessage message, CancellationToken cancellationToken)
|
||||
protected override bool TryValidateMessage(
|
||||
SendEmailMessage message,
|
||||
RabbitMqMessageContext context,
|
||||
out string? validationError)
|
||||
{
|
||||
using var scope = _scopeFactory.CreateScope();
|
||||
var service = scope.ServiceProvider.GetRequiredService<ISendEmailService>();
|
||||
validationError = SendEmailDeliveryValidator.GetValidationError(
|
||||
message,
|
||||
context,
|
||||
SupportedMessageType);
|
||||
return validationError is null;
|
||||
}
|
||||
|
||||
protected override async Task HandleMessageAsync(
|
||||
SendEmailMessage message,
|
||||
RabbitMqMessageContext context,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
string payloadCorrelationId = message.CorrelationContext.CorrelationId;
|
||||
if (!string.IsNullOrWhiteSpace(context.CorrelationId) &&
|
||||
!string.Equals(context.CorrelationId, payloadCorrelationId, StringComparison.Ordinal))
|
||||
{
|
||||
_logger.LogWarning(
|
||||
"Broker and payload correlation IDs differ; payload correlation ID will be used");
|
||||
}
|
||||
|
||||
await using AsyncServiceScope scope = _scopeFactory.CreateAsyncScope();
|
||||
ISendEmailService service = scope.ServiceProvider.GetRequiredService<ISendEmailService>();
|
||||
await service.ProcessAsync(message, cancellationToken);
|
||||
}
|
||||
|
||||
protected override async Task HandleMessageRetriesExhaustedAsync(
|
||||
SendEmailMessage message,
|
||||
RabbitMqMessageContext context,
|
||||
Exception exception,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
string deliveryError = NotificationDeliveryErrorFormatter.Format(exception);
|
||||
await using AsyncServiceScope scope = _scopeFactory.CreateAsyncScope();
|
||||
INotificationResultPublisher resultPublisher =
|
||||
scope.ServiceProvider.GetRequiredService<INotificationResultPublisher>();
|
||||
await resultPublisher.PublishAsync(message, deliveryError, cancellationToken);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
namespace HrynCo.NotificationService.Worker;
|
||||
|
||||
using HrynCo.NotificationService.Contracts.Messages;
|
||||
using HrynCo.NotificationService.Worker.Services.EmailProcessing;
|
||||
using Hrynco.RabbitMq;
|
||||
|
||||
internal static class SendEmailDeliveryValidator
|
||||
{
|
||||
public static string? GetValidationError(
|
||||
SendEmailMessage message,
|
||||
RabbitMqMessageContext context,
|
||||
string supportedMessageType)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(context.MessageId))
|
||||
return "AMQP MessageId is required.";
|
||||
|
||||
if (!string.Equals(context.MessageType, supportedMessageType, StringComparison.Ordinal))
|
||||
return $"Unsupported AMQP message type '{context.MessageType ?? "<missing>"}'.";
|
||||
|
||||
try
|
||||
{
|
||||
SendEmailMessageValidator.Validate(message);
|
||||
return null;
|
||||
}
|
||||
catch (InvalidDataException exception)
|
||||
{
|
||||
return exception.Message;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user