diff --git a/OrderProcessing.Api/BackgroundJobs/EmailBackgroundService.cs b/OrderProcessing.Api/BackgroundJobs/EmailBackgroundService.cs deleted file mode 100644 index a920835..0000000 --- a/OrderProcessing.Api/BackgroundJobs/EmailBackgroundService.cs +++ /dev/null @@ -1,70 +0,0 @@ -using OrderProcessing.Api.Services.Emailing; - -namespace OrderProcessing.Api.BackgroundJobs; - -public sealed class EmailBackgroundService : BackgroundService -{ - private readonly IEmailQueue _emailQueue; - private readonly IServiceScopeFactory _scopeFactory; - private readonly ILogger _logger; - - public EmailBackgroundService( - IEmailQueue emailQueue, - IServiceScopeFactory scopeFactory, - ILogger logger) - { - _emailQueue = emailQueue; - _scopeFactory = scopeFactory; - _logger = logger; - } - - protected override async Task ExecuteAsync(CancellationToken stoppingToken) - { - _logger.LogInformation("Email background service started"); - - while (!stoppingToken.IsCancellationRequested) - { - EmailMessage message; - - try - { - message = await _emailQueue.DequeueAsync(stoppingToken); - } - catch (OperationCanceledException) - when (stoppingToken.IsCancellationRequested) - { - break; - } - - try - { - await using var scope = _scopeFactory.CreateAsyncScope(); - - var emailSender = scope.ServiceProvider - .GetRequiredService(); - - await emailSender.SendAsync(message, stoppingToken); - - _logger.LogInformation( - "Queued email processed successfully. Recipient: {Recipient}, Subject: {Subject}", - message.To, - message.Subject); - } - catch (OperationCanceledException) - when (stoppingToken.IsCancellationRequested) - { - break; - } - catch (Exception exception) - { - _logger.LogError( - exception, - "Failed to process queued email. Recipient: {Recipient}, Subject: {Subject}", - message.To, - message.Subject); - } - } - - _logger.LogInformation("Email background service stopped"); - } -} \ No newline at end of file diff --git a/OrderProcessing.Api/Features/Orders/Commands/CompleteOrder/CompleteOrderCommandHandler.cs b/OrderProcessing.Api/Features/Orders/Commands/CompleteOrder/CompleteOrderCommandHandler.cs index fcfae18..3b3b98f 100644 --- a/OrderProcessing.Api/Features/Orders/Commands/CompleteOrder/CompleteOrderCommandHandler.cs +++ b/OrderProcessing.Api/Features/Orders/Commands/CompleteOrder/CompleteOrderCommandHandler.cs @@ -5,7 +5,6 @@ using OrderProcessing.Api.Entities; using OrderProcessing.Api.Exceptions; using OrderProcessing.Api.Services.Auditing; -using OrderProcessing.Api.Services.Emailing; using OrderProcessing.Api.Services.Outbox; using OrderProcessing.Contracts.Orders; @@ -16,20 +15,17 @@ public sealed class CompleteOrderCommandHandler { private readonly OrderProcessingDbContext _dbContext; private readonly IAuditService _auditService; - private readonly IEmailQueue _emailQueue; private readonly ILogger _logger; private readonly IOutboxWriter _outboxWriter; public CompleteOrderCommandHandler( OrderProcessingDbContext dbContext, IAuditService auditService, - IEmailQueue emailQueue, ILogger logger, IOutboxWriter outboxWriter) { _dbContext = dbContext; _auditService = auditService; - _emailQueue = emailQueue; _logger = logger; _outboxWriter = outboxWriter; } @@ -88,85 +84,9 @@ public async Task Handle( _logger.LogInformation( "Order {OrderId} completed", order.Id); - var emailMessage = CreateOrderCompletedEmail(order); - - await TryEnqueueEmailAsync( - emailMessage, - order.Id, - cancellationToken); - return MapToResponse(order); } - private static EmailMessage CreateOrderCompletedEmail( - Order order) - { - var customerName = - $"{order.Customer.FirstName} {order.Customer.LastName}"; - - var itemLines = order.Items.Select(item => - $"- {item.ProductName}: {item.Quantity} × " + - $"{item.UnitPrice:F2} = {item.LineTotal:F2}"); - - var body = $""" - Hello {customerName}, - - Your order #{order.Id} has been completed successfully. - - Items: - {string.Join(Environment.NewLine, itemLines)} - - Total amount: {order.TotalAmount:F2} - Status: {order.Status} - Completed at: {order.CompletedAtUtc:yyyy-MM-dd HH:mm} UTC - - Thank you. - """; - - return new EmailMessage - { - To = order.Customer.Email, - Subject = $"Order #{order.Id} completed", - Body = body, - IsHtml = false - }; - } - - private async Task TryEnqueueEmailAsync( - EmailMessage message, - int orderId, - CancellationToken cancellationToken) - { - try - { - await _emailQueue.EnqueueAsync( - message, - cancellationToken); - - _logger.LogInformation( - "Order-completed email queued. " + - "OrderId: {OrderId}, Recipient: {Recipient}", - orderId, - message.To); - } - catch (OperationCanceledException) - when (cancellationToken.IsCancellationRequested) - { - _logger.LogWarning( - "Order {OrderId} was completed, but email " + - "enqueueing was cancelled", - orderId); - } - catch (Exception exception) - { - _logger.LogError( - exception, - "Order {OrderId} was completed, but its email " + - "could not be queued", - orderId); - } - } - private static OrderResponse MapToResponse(Order order) { return new OrderResponse diff --git a/OrderProcessing.Api/Features/Orders/Commands/CreateOrder/CreateOrderCommandHandler.cs b/OrderProcessing.Api/Features/Orders/Commands/CreateOrder/CreateOrderCommandHandler.cs index 59cd75c..2f557d4 100644 --- a/OrderProcessing.Api/Features/Orders/Commands/CreateOrder/CreateOrderCommandHandler.cs +++ b/OrderProcessing.Api/Features/Orders/Commands/CreateOrder/CreateOrderCommandHandler.cs @@ -5,7 +5,6 @@ using OrderProcessing.Api.Entities; using OrderProcessing.Api.Exceptions; using OrderProcessing.Api.Services.Auditing; -using OrderProcessing.Api.Services.Emailing; using OrderProcessing.Api.Services.Outbox; using OrderProcessing.Contracts.Orders; @@ -15,20 +14,17 @@ public sealed class CreateOrderCommandHandler : IRequestHandler _logger; private readonly IOutboxWriter _outboxWriter; public CreateOrderCommandHandler( OrderProcessingDbContext dbContext, IAuditService auditService, - IEmailQueue emailQueue, ILogger logger, IOutboxWriter outboxWriter) { _dbContext = dbContext; _auditService = auditService; - _emailQueue = emailQueue; _logger = logger; _outboxWriter = outboxWriter; } @@ -132,14 +128,6 @@ public async Task Handle(CreateOrderCommand command, Cancellation order.CustomerId, order.TotalAmount); - var emailMessage = CreateOrderCreatedEmail(order, customer); - - await TryEnqueueEmailAsync( - emailMessage, - order.Id, - "Order Created", - cancellationToken); - return MapToResponse(order, $"{customer.FirstName} {customer.LastName}"); } catch @@ -273,71 +261,6 @@ private async Task GetCustomerSummaryAsync( ?? throw new NotFoundException($"Customer with id {customerId} was not found."); } - private static EmailMessage CreateOrderCreatedEmail( - Order order, - CustomerSummary customer) - { - var customerName = $"{customer.FirstName} {customer.LastName}"; - - var itemLines = order.Items.Select(item => - $"- {item.ProductName}: {item.Quantity} × {item.UnitPrice:F2} = {item.LineTotal:F2}"); - - var body = $""" - Hello {customerName}, - - Your order #{order.Id} has been created successfully. - - Items: - {string.Join(Environment.NewLine, itemLines)} - - Total amount: {order.TotalAmount:F2} - Status: {order.Status} - - Thank you. - """; - - return new EmailMessage - { - To = customer.Email, - Subject = $"Order #{order.Id} created", - Body = body, - IsHtml = false - }; - } - - private async Task TryEnqueueEmailAsync( - EmailMessage message, - int orderId, - string emailType, - CancellationToken cancellationToken) - { - try - { - await _emailQueue.EnqueueAsync(message, cancellationToken); - - _logger.LogInformation( - "{EmailType} email queued. OrderId: {OrderId}, Recipient: {Recipient}", - emailType, - orderId, - message.To); - } - catch (OperationCanceledException) - when (cancellationToken.IsCancellationRequested) - { - _logger.LogWarning( - "Order {OrderId} was saved, but enqueueing the {EmailType} email was cancelled", - orderId, - emailType); - } - catch (Exception exception) - { - _logger.LogError( - exception, - "Order {OrderId} was saved, but its {EmailType} email could not be queued", - orderId, - emailType); - } - } private static OrderCreatedIntegrationEvent CreateOrderCreatedIntegrationEvent(Order order, CustomerSummary customer) { diff --git a/OrderProcessing.Api/Program.cs b/OrderProcessing.Api/Program.cs index 01355a8..bacc8ac 100644 --- a/OrderProcessing.Api/Program.cs +++ b/OrderProcessing.Api/Program.cs @@ -5,7 +5,6 @@ using OrderProcessing.Api.Extensions; using OrderProcessing.Api.Services.Auditing; using OrderProcessing.Api.Services.Customers; -using OrderProcessing.Api.Services.Emailing; using OrderProcessing.Api.Services.Messaging; using OrderProcessing.Api.Services.Outbox; using OrderProcessing.Api.Services.Products; @@ -42,11 +41,6 @@ public static async Task Main(string[] args) //builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); - builder.Services.AddScoped(); - builder.Services.AddSingleton( - _ => new EmailQueue(capacity: 100)); - - builder.Services.AddHostedService(); if (!builder.Environment.IsEnvironment("Testing")) { diff --git a/OrderProcessing.Api/Services/Emailing/EmailMessage.cs b/OrderProcessing.Api/Services/Emailing/EmailMessage.cs deleted file mode 100644 index 98bdcd5..0000000 --- a/OrderProcessing.Api/Services/Emailing/EmailMessage.cs +++ /dev/null @@ -1,12 +0,0 @@ -namespace OrderProcessing.Api.Services.Emailing; - -public sealed record EmailMessage -{ - public required string To { get; init; } - - public required string Subject { get; init; } - - public required string Body { get; init; } - - public bool IsHtml { get; init; } -} \ No newline at end of file diff --git a/OrderProcessing.Api/Services/Emailing/EmailQueue.cs b/OrderProcessing.Api/Services/Emailing/EmailQueue.cs deleted file mode 100644 index d56b76c..0000000 --- a/OrderProcessing.Api/Services/Emailing/EmailQueue.cs +++ /dev/null @@ -1,38 +0,0 @@ -using System.Threading.Channels; - -namespace OrderProcessing.Api.Services.Emailing; - -public sealed class EmailQueue : IEmailQueue -{ - private readonly Channel _channel; - - public EmailQueue(int capacity) - { - ArgumentOutOfRangeException.ThrowIfNegativeOrZero(capacity); - - var options = new BoundedChannelOptions(capacity) - { - FullMode = BoundedChannelFullMode.Wait, - SingleReader = true, - SingleWriter = false, - AllowSynchronousContinuations = false - }; - - _channel = Channel.CreateBounded(options); - } - - public ValueTask EnqueueAsync( - EmailMessage message, - CancellationToken cancellationToken = default) - { - ArgumentNullException.ThrowIfNull(message); - - return _channel.Writer.WriteAsync(message, cancellationToken); - } - - public ValueTask DequeueAsync( - CancellationToken cancellationToken = default) - { - return _channel.Reader.ReadAsync(cancellationToken); - } -} \ No newline at end of file diff --git a/OrderProcessing.Api/Services/Emailing/IEmailQueue.cs b/OrderProcessing.Api/Services/Emailing/IEmailQueue.cs deleted file mode 100644 index 5b2d46a..0000000 --- a/OrderProcessing.Api/Services/Emailing/IEmailQueue.cs +++ /dev/null @@ -1,11 +0,0 @@ -namespace OrderProcessing.Api.Services.Emailing; - -public interface IEmailQueue -{ - ValueTask EnqueueAsync( - EmailMessage message, - CancellationToken cancellationToken = default); - - ValueTask DequeueAsync( - CancellationToken cancellationToken = default); -} \ No newline at end of file diff --git a/OrderProcessing.Api/Services/Emailing/IEmailSender.cs b/OrderProcessing.Api/Services/Emailing/IEmailSender.cs deleted file mode 100644 index 66f6d84..0000000 --- a/OrderProcessing.Api/Services/Emailing/IEmailSender.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace OrderProcessing.Api.Services.Emailing; - -public interface IEmailSender -{ - Task SendAsync( - EmailMessage message, - CancellationToken cancellationToken = default); -} \ No newline at end of file diff --git a/OrderProcessing.Api/Services/Emailing/LoggingEmailSender.cs b/OrderProcessing.Api/Services/Emailing/LoggingEmailSender.cs deleted file mode 100644 index 58bc0bd..0000000 --- a/OrderProcessing.Api/Services/Emailing/LoggingEmailSender.cs +++ /dev/null @@ -1,28 +0,0 @@ -namespace OrderProcessing.Api.Services.Emailing; - -public sealed class LoggingEmailSender : IEmailSender -{ - private readonly ILogger _logger; - - public LoggingEmailSender(ILogger logger) - { - _logger = logger; - } - - public Task SendAsync( - EmailMessage message, - CancellationToken cancellationToken = default) - { - ArgumentNullException.ThrowIfNull(message); - - cancellationToken.ThrowIfCancellationRequested(); - - _logger.LogInformation( - "Email delivery simulated. Recipient: {Recipient}, Subject: {Subject}, IsHtml: {IsHtml}", - message.To, - message.Subject, - message.IsHtml); - - return Task.CompletedTask; - } -} \ No newline at end of file