refactor(infrastructure): Refactor RabbitMqEmailQueue and remove InMemoryEmailQueue, update DbContext
This commit is contained in:
@@ -1,4 +1,3 @@
|
|||||||
using DigitalData.EmailProfiler.Domain.Entities;
|
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
|
||||||
namespace DigitalData.EmailProfiler.Infrastructure.Persistence;
|
namespace DigitalData.EmailProfiler.Infrastructure.Persistence;
|
||||||
@@ -9,13 +8,4 @@ namespace DigitalData.EmailProfiler.Infrastructure.Persistence;
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public class EmailProfilerDbContext(DbContextOptions<EmailProfilerDbContext> options) : DbContext(options)
|
public class EmailProfilerDbContext(DbContextOptions<EmailProfilerDbContext> options) : DbContext(options)
|
||||||
{
|
{
|
||||||
// DbSets for all entities
|
|
||||||
public DbSet<EmailAccount> EmailAccounts { get; set; }
|
|
||||||
public DbSet<EmailProfile> EmailProfiles { get; set; }
|
|
||||||
public DbSet<EmailHistory> EmailHistories { get; set; }
|
|
||||||
public DbSet<EmailAttachment> EmailAttachments { get; set; }
|
|
||||||
public DbSet<EmailProcess> EmailProcesses { get; set; }
|
|
||||||
public DbSet<ProcessStep> ProcessSteps { get; set; }
|
|
||||||
public DbSet<IndexingStep> IndexingSteps { get; set; }
|
|
||||||
public DbSet<EmailOutbox> EmailOutbox { get; set; }
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,59 +0,0 @@
|
|||||||
using System.Threading.Channels;
|
|
||||||
using DigitalData.EmailProfiler.Application.Common.Interfaces;
|
|
||||||
using DigitalData.EmailProfiler.Domain.Entities;
|
|
||||||
|
|
||||||
namespace DigitalData.EmailProfiler.Infrastructure.Queue;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// In-memory email queue implementation using System.Threading.Channels.
|
|
||||||
/// Thread-safe, high-performance queue for outgoing emails.
|
|
||||||
///
|
|
||||||
/// NOTE: This class is OBSOLETE. Use RabbitMqEmailQueue for production.
|
|
||||||
/// InMemoryEmailQueue does not persist messages and will lose data on application restart.
|
|
||||||
/// </summary>
|
|
||||||
[Obsolete("InMemoryEmailQueue is obsolete. Use RabbitMqEmailQueue for production deployment.")]
|
|
||||||
public class InMemoryEmailQueue : IEmailQueue
|
|
||||||
{
|
|
||||||
private readonly Channel<EmailOutbox> _channel;
|
|
||||||
|
|
||||||
public InMemoryEmailQueue()
|
|
||||||
{
|
|
||||||
var options = new BoundedChannelOptions(1000)
|
|
||||||
{
|
|
||||||
FullMode = BoundedChannelFullMode.Wait
|
|
||||||
};
|
|
||||||
|
|
||||||
_channel = Channel.CreateBounded<EmailOutbox>(options);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task EnqueueAsync(EmailOutbox email, CancellationToken cancellationToken = default)
|
|
||||||
{
|
|
||||||
await _channel.Writer.WriteAsync(email, cancellationToken);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<EmailOutbox?> DequeueAsync(CancellationToken cancellationToken = default)
|
|
||||||
{
|
|
||||||
if (await _channel.Reader.WaitToReadAsync(cancellationToken))
|
|
||||||
{
|
|
||||||
if (_channel.Reader.TryRead(out var email))
|
|
||||||
{
|
|
||||||
return email;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Task<int> GetQueueDepthAsync(CancellationToken cancellationToken = default)
|
|
||||||
{
|
|
||||||
return Task.FromResult(_channel.Reader.Count);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// NOT IMPLEMENTED - InMemoryEmailQueue does not support event-driven consumers
|
|
||||||
/// </summary>
|
|
||||||
public Task StartConsumerAsync(Func<EmailOutbox, Task> onMessageReceived, CancellationToken cancellationToken = default)
|
|
||||||
{
|
|
||||||
throw new NotSupportedException("InMemoryEmailQueue does not support StartConsumerAsync. Use RabbitMqEmailQueue for event-driven consumers.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,13 +1,13 @@
|
|||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
using DigitalData.EmailProfiler.Application.Common.Interfaces;
|
using DigitalData.EmailProfiler.Application.Common.Interfaces;
|
||||||
using DigitalData.EmailProfiler.Domain.Common;
|
using DigitalData.EmailProfiler.Application.EmailSending.Commands;
|
||||||
using DigitalData.EmailProfiler.Domain.Entities;
|
|
||||||
using DigitalData.EmailProfiler.Infrastructure.Messaging;
|
using DigitalData.EmailProfiler.Infrastructure.Messaging;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using Microsoft.Extensions.Options;
|
using Microsoft.Extensions.Options;
|
||||||
using RabbitMQ.Client;
|
using RabbitMQ.Client;
|
||||||
using RabbitMQ.Client.Events;
|
using RabbitMQ.Client.Events;
|
||||||
|
using DigitalData.EmailProfiler.Application.Common.Events;
|
||||||
|
|
||||||
namespace DigitalData.EmailProfiler.Infrastructure.Queue;
|
namespace DigitalData.EmailProfiler.Infrastructure.Queue;
|
||||||
|
|
||||||
@@ -126,11 +126,11 @@ public class RabbitMqEmailQueue : IEmailQueue, IDisposable
|
|||||||
await _initializationTask.Value;
|
await _initializationTask.Value;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task EnqueueAsync(EmailOutbox email, CancellationToken cancellationToken = default)
|
public async Task EnqueueAsync(OutgoingEmailEvent outgoingEmailEvent, CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
await EnsureInitializedAsync(); // Initialize on first call
|
await EnsureInitializedAsync(); // Initialize on first call
|
||||||
|
|
||||||
var json = JsonSerializer.Serialize(email);
|
var json = JsonSerializer.Serialize(outgoingEmailEvent);
|
||||||
var body = Encoding.UTF8.GetBytes(json);
|
var body = Encoding.UTF8.GetBytes(json);
|
||||||
|
|
||||||
var properties = new BasicProperties
|
var properties = new BasicProperties
|
||||||
@@ -149,7 +149,7 @@ public class RabbitMqEmailQueue : IEmailQueue, IDisposable
|
|||||||
cancellationToken: cancellationToken);
|
cancellationToken: cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<EmailOutbox?> DequeueAsync(CancellationToken cancellationToken = default)
|
public async Task<OutgoingEmailEvent?> DequeueAsync(CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
await EnsureInitializedAsync(); // Initialize on first call
|
await EnsureInitializedAsync(); // Initialize on first call
|
||||||
|
|
||||||
@@ -161,7 +161,7 @@ public class RabbitMqEmailQueue : IEmailQueue, IDisposable
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
var json = Encoding.UTF8.GetString(result.Body.ToArray());
|
var json = Encoding.UTF8.GetString(result.Body.ToArray());
|
||||||
var email = JsonSerializer.Deserialize<EmailOutbox>(json);
|
var email = JsonSerializer.Deserialize<OutgoingEmailEvent>(json);
|
||||||
|
|
||||||
// Acknowledge message after successful deserialization
|
// Acknowledge message after successful deserialization
|
||||||
await _channel.BasicAckAsync(result.DeliveryTag, false, cancellationToken);
|
await _channel.BasicAckAsync(result.DeliveryTag, false, cancellationToken);
|
||||||
@@ -188,7 +188,7 @@ public class RabbitMqEmailQueue : IEmailQueue, IDisposable
|
|||||||
/// Start event-driven consumer that processes messages as they arrive
|
/// Start event-driven consumer that processes messages as they arrive
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public async Task StartConsumerAsync(
|
public async Task StartConsumerAsync(
|
||||||
Func<EmailOutbox, Task> onMessageReceived,
|
Func<OutgoingEmailEvent, Task> onMailReceived,
|
||||||
CancellationToken cancellationToken = default)
|
CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
await EnsureInitializedAsync(); // Initialize on first call
|
await EnsureInitializedAsync(); // Initialize on first call
|
||||||
@@ -200,14 +200,14 @@ public class RabbitMqEmailQueue : IEmailQueue, IDisposable
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
var json = Encoding.UTF8.GetString(args.Body.ToArray());
|
var json = Encoding.UTF8.GetString(args.Body.ToArray());
|
||||||
var email = JsonSerializer.Deserialize<EmailOutbox>(json);
|
var email = JsonSerializer.Deserialize<OutgoingEmailEvent>(json);
|
||||||
|
|
||||||
if (email != null)
|
if (email != null)
|
||||||
{
|
{
|
||||||
_logger.LogDebug("Received email message: To={To}, Subject={Subject}", email.Recipient, email.Subject);
|
_logger.LogDebug("Received email message: To={To}, Subject={Subject}", email.Recipient, email.Subject);
|
||||||
|
|
||||||
// Process message via callback
|
// Process message via callback
|
||||||
await onMessageReceived(email);
|
await onMailReceived(email);
|
||||||
|
|
||||||
// Acknowledge message after successful processing
|
// Acknowledge message after successful processing
|
||||||
await _channel.BasicAckAsync(args.DeliveryTag, false, cancellationToken);
|
await _channel.BasicAckAsync(args.DeliveryTag, false, cancellationToken);
|
||||||
@@ -225,7 +225,7 @@ public class RabbitMqEmailQueue : IEmailQueue, IDisposable
|
|||||||
|
|
||||||
// TODO: Error Reporting Strategy
|
// TODO: Error Reporting Strategy
|
||||||
// Option 1: Separate RabbitMQ Queue (emailprofiler.errors)
|
// Option 1: Separate RabbitMQ Queue (emailprofiler.errors)
|
||||||
// - Create EmailErrorReport entity { EmailOutboxId, Exception, StackTrace, Timestamp, RetryAttempt }
|
// - Create EmailErrorReport entity { OutgoingEmailEventId, Exception, StackTrace, Timestamp, RetryAttempt }
|
||||||
// - Publish to error queue: await _errorQueue.EnqueueAsync(errorReport)
|
// - Publish to error queue: await _errorQueue.EnqueueAsync(errorReport)
|
||||||
// - Separate worker processes error queue → Log to DB/File/External monitoring
|
// - Separate worker processes error queue → Log to DB/File/External monitoring
|
||||||
//
|
//
|
||||||
|
|||||||
Reference in New Issue
Block a user