docs: Update AGENTS.md and STATUS.md with RabbitMQ and Phase 2 completion
AGENTS.md: - Add Section 7: RabbitMQ Command Bus Integration (IMPLEMENTED) - Document ICommandPublisher, RabbitMqCommandPublisher, RabbitMqCommandConsumer - Add configuration, DI setup, and usage examples - Document benefits: async processing, horizontal scaling, retries, persistence STATUS.md: - Mark Phase 2 (Application Layer) as 100% complete - Update Phase 3 (Infrastructure Layer) to 15% (RabbitMQ done) - Document all completed components: DTOs, Commands, Queries, Validators, Mappings - Update last modified date to 2026-07-14
This commit is contained in:
194
AGENTS.md
194
AGENTS.md
@@ -82,18 +82,200 @@ public class CreateEmailProfileCommandHandler : IRequestHandler<CreateEmailProfi
|
|||||||
- Commands: `{Verb}{Entity}Command.cs` (e.g., `CreateEmailProfileCommand.cs`)
|
- Commands: `{Verb}{Entity}Command.cs` (e.g., `CreateEmailProfileCommand.cs`)
|
||||||
- Queries: `{Verb}{Entity}Query.cs` (e.g., `GetEmailProfilesQuery.cs`)
|
- Queries: `{Verb}{Entity}Query.cs` (e.g., `GetEmailProfilesQuery.cs`)
|
||||||
|
|
||||||
|
### 6. Repository Pattern - NO UnitOfWork, Generic CRUD with AutoMapper
|
||||||
|
**CRITICAL**: DO NOT use IUnitOfWork pattern. Use generic repository pattern with AutoMapper-based CRUD operations.
|
||||||
|
|
||||||
|
**Key Principles**:
|
||||||
|
- ✅ Each operation auto-saves changes - NO explicit SaveChangesAsync needed
|
||||||
|
- ✅ Use `UpdateSingleAsync` / `DeleteSingleAsync` for single-record safety
|
||||||
|
- ✅ Use `UpdateAsync` / `DeleteAsync` only when intentionally modifying multiple records
|
||||||
|
- ✅ AutoMapper handles all DTO → Entity mappings
|
||||||
|
|
||||||
|
**Pattern**:
|
||||||
|
```csharp
|
||||||
|
// IRepository<T> generic interface
|
||||||
|
public interface IRepository<TEntity> where TEntity : class
|
||||||
|
{
|
||||||
|
// Query operations
|
||||||
|
Task<TEntity?> GetByIdAsync(int id, CancellationToken cancellationToken = default);
|
||||||
|
Task<IEnumerable<TEntity>> FindAsync(Expression<Func<TEntity, bool>> predicate, ...);
|
||||||
|
|
||||||
|
// Create - auto-saves
|
||||||
|
Task<TEntity> CreateAsync<TDto>(TDto dto, CancellationToken cancellationToken = default);
|
||||||
|
|
||||||
|
// Update - auto-saves
|
||||||
|
Task<int> UpdateAsync<TDto>(Expression<...> predicate, TDto dto, ...); // Multiple records
|
||||||
|
Task UpdateSingleAsync<TDto>(Expression<...> predicate, TDto dto, ...); // SAFE: Single record only
|
||||||
|
|
||||||
|
// Delete - auto-saves
|
||||||
|
Task<int> DeleteAsync(Expression<...> predicate, ...); // Multiple records
|
||||||
|
Task DeleteSingleAsync(Expression<...> predicate, ...); // SAFE: Single record only
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Command Handler Examples**:
|
||||||
|
```csharp
|
||||||
|
// ✅ CORRECT - CreateAsync auto-saves
|
||||||
|
public class CreateEmailProfileCommandHandler(IRepository<EmailProfile> repository)
|
||||||
|
: IRequestHandler<CreateEmailProfileCommand, int>
|
||||||
|
{
|
||||||
|
public async Task<int> Handle(CreateEmailProfileCommand request, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
var profile = await repository.CreateAsync(request, cancellationToken);
|
||||||
|
return profile.Id; // NO SaveChangesAsync needed!
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ✅ CORRECT - UpdateSingleAsync for safety (throws if 0 or 2+ records match)
|
||||||
|
public class UpdateEmailProfileCommandHandler(IRepository<EmailProfile> repository)
|
||||||
|
: IRequestHandler<UpdateEmailProfileCommand, int>
|
||||||
|
{
|
||||||
|
public async Task<int> Handle(UpdateEmailProfileCommand request, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
await repository.UpdateSingleAsync(p => p.Id == request.Id, request, cancellationToken);
|
||||||
|
return request.Id; // NO SaveChangesAsync needed!
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ✅ CORRECT - DeleteSingleAsync for safety (throws if 0 or 2+ records match)
|
||||||
|
public class DeleteEmailProfileCommandHandler(IRepository<EmailProfile> repository)
|
||||||
|
: IRequestHandler<DeleteEmailProfileCommand, int>
|
||||||
|
{
|
||||||
|
public async Task<int> Handle(DeleteEmailProfileCommand request, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
await repository.DeleteSingleAsync(p => p.Id == request.Id, cancellationToken);
|
||||||
|
return request.Id; // NO SaveChangesAsync needed!
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ❌ WRONG - Manual entity creation (use AutoMapper instead)
|
||||||
|
var profile = new EmailProfile
|
||||||
|
{
|
||||||
|
ProfileName = request.ProfileName,
|
||||||
|
EmailAccountId = request.EmailAccountId,
|
||||||
|
// ... 15 more properties
|
||||||
|
};
|
||||||
|
|
||||||
|
// ❌ WRONG - Using IUnitOfWork (removed)
|
||||||
|
public CreateEmailProfileCommandHandler(IUnitOfWork unitOfWork) { ... }
|
||||||
|
|
||||||
|
// ❌ WRONG - Calling SaveChangesAsync (removed)
|
||||||
|
await repository.SaveChangesAsync(cancellationToken);
|
||||||
|
```
|
||||||
|
|
||||||
|
**Safety Rules**:
|
||||||
|
1. **UpdateSingleAsync** - Use for ID-based updates. Throws `InvalidOperationException` if:
|
||||||
|
- Zero records match (entity not found)
|
||||||
|
- Multiple records match (predicate too broad)
|
||||||
|
|
||||||
|
2. **DeleteSingleAsync** - Use for ID-based deletes. Throws `InvalidOperationException` if:
|
||||||
|
- Zero records match (entity not found)
|
||||||
|
- Multiple records match (predicate too broad)
|
||||||
|
|
||||||
|
3. **UpdateAsync / DeleteAsync** - Use ONLY when intentionally modifying multiple records:
|
||||||
|
```csharp
|
||||||
|
// ✅ CORRECT - Intentional bulk operation
|
||||||
|
await repository.UpdateAsync(
|
||||||
|
p => p.EmailAccountId == accountId,
|
||||||
|
new { IsActive = false },
|
||||||
|
cancellationToken);
|
||||||
|
|
||||||
|
// ✅ Returns count of updated/deleted records
|
||||||
|
var count = await repository.DeleteAsync(p => p.IsActive == false, cancellationToken);
|
||||||
|
```
|
||||||
|
|
||||||
|
**DTO Mapping Responsibility**:
|
||||||
|
- Each DTO creator must define their own AutoMapper profile
|
||||||
|
- Example: `CreateEmailProfileCommand` → `EmailProfile` mapping must be defined in `EmailProfileMappingProfile.cs`
|
||||||
|
- Repository implementation uses `IMapper.Map<TEntity>(dto)` internally
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Future Enhancements
|
## Future Enhancements
|
||||||
|
|
||||||
### HIGH PRIORITY: RabbitMQ Queue Implementation
|
### 7. RabbitMQ Command Bus Integration (IMPLEMENTED)
|
||||||
|
|
||||||
**Current State**:
|
**Purpose**: Asynchronous command processing via RabbitMQ message broker for POST/PUT/DELETE operations.
|
||||||
- Email queue is implemented using in-memory `Channel<T>` in `InMemoryEmailQueue.cs`
|
|
||||||
- Location: `src/DigitalData.EmailProfiler.Infrastructure/Queue/InMemoryEmailQueue.cs`
|
|
||||||
|
|
||||||
**Future Enhancement**:
|
**Architecture**:
|
||||||
Replace the in-memory queue with **RabbitMQ** for production resilience and scalability.
|
- **GET Queries**: Synchronous (immediate response via MediatR)
|
||||||
|
- **POST/PUT/DELETE Commands**: Can be asynchronous (published to RabbitMQ, processed by background worker)
|
||||||
|
|
||||||
|
**RabbitMQ Server**:
|
||||||
|
- Management UI: `http://172.24.12.56:15672`
|
||||||
|
- AMQP Port: `5672` (default)
|
||||||
|
- Exchange: `emailprofiler.commands` (Direct)
|
||||||
|
- Queue: `emailprofiler.command.queue`
|
||||||
|
- Routing Key: `command`
|
||||||
|
|
||||||
|
**Implementation Components**:
|
||||||
|
|
||||||
|
1. **ICommandPublisher** (`Application/Common/Interfaces/ICommandPublisher.cs`):
|
||||||
|
- Interface for publishing commands to message broker
|
||||||
|
- Generic method: `PublishAsync<TCommand>(TCommand command, CancellationToken)`
|
||||||
|
|
||||||
|
2. **RabbitMqCommandPublisher** (`Infrastructure/Messaging/RabbitMqCommandPublisher.cs`):
|
||||||
|
- Implements `ICommandPublisher`
|
||||||
|
- Serializes command to JSON with metadata envelope (CommandType, Payload, CorrelationId, PublishedAt)
|
||||||
|
- Publishes to RabbitMQ exchange with persistent delivery mode
|
||||||
|
|
||||||
|
3. **RabbitMqCommandConsumer** (`Infrastructure/Messaging/RabbitMqCommandConsumer.cs`):
|
||||||
|
- BackgroundService that consumes commands from RabbitMQ
|
||||||
|
- Deserializes command envelope
|
||||||
|
- Resolves command type from assembly
|
||||||
|
- Executes command via MediatR in scoped service
|
||||||
|
- Acknowledges message on success, requeues on error
|
||||||
|
|
||||||
|
4. **RabbitMqConfiguration** (`Infrastructure/Messaging/RabbitMqConfiguration.cs`):
|
||||||
|
- Configuration model for RabbitMQ connection
|
||||||
|
- Binds to `appsettings.json` section: `RabbitMq`
|
||||||
|
|
||||||
|
**Configuration** (`appsettings.json`):
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"RabbitMq": {
|
||||||
|
"HostName": "172.24.12.56",
|
||||||
|
"Port": 5672,
|
||||||
|
"UserName": "guest",
|
||||||
|
"Password": "guest",
|
||||||
|
"VirtualHost": "/",
|
||||||
|
"ExchangeName": "emailprofiler.commands",
|
||||||
|
"QueueName": "emailprofiler.command.queue",
|
||||||
|
"RoutingKey": "command",
|
||||||
|
"AutomaticRecoveryEnabled": true,
|
||||||
|
"NetworkRecoveryIntervalSeconds": 10
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Dependency Injection** (`Infrastructure/DependencyInjection.cs`):
|
||||||
|
```csharp
|
||||||
|
services.Configure<RabbitMqConfiguration>(configuration.GetSection(RabbitMqConfiguration.SectionName));
|
||||||
|
services.AddSingleton<ICommandPublisher, RabbitMqCommandPublisher>();
|
||||||
|
services.AddHostedService<RabbitMqCommandConsumer>();
|
||||||
|
```
|
||||||
|
|
||||||
|
**Usage in API Controllers** (Future):
|
||||||
|
```csharp
|
||||||
|
// Option 1: Synchronous (immediate execution via MediatR)
|
||||||
|
var result = await _mediator.Send(new CreateEmailProfileCommand(...), cancellationToken);
|
||||||
|
return Ok(result);
|
||||||
|
|
||||||
|
// Option 2: Asynchronous (publish to RabbitMQ for background processing)
|
||||||
|
await _commandPublisher.PublishAsync(new CreateEmailProfileCommand(...), cancellationToken);
|
||||||
|
return Accepted(); // HTTP 202 - command queued for processing
|
||||||
|
```
|
||||||
|
|
||||||
|
**Benefits**:
|
||||||
|
- Decouples API from long-running command processing
|
||||||
|
- Improves API responsiveness (fire-and-forget)
|
||||||
|
- Enables horizontal scaling (multiple consumers)
|
||||||
|
- Automatic retries on failure (requeue mechanism)
|
||||||
|
- Message persistence (survives application restarts)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### HIGH PRIORITY: Email Queue for Outgoing Messages (Future)
|
||||||
|
|
||||||
**Implementation Steps**:
|
**Implementation Steps**:
|
||||||
|
|
||||||
|
|||||||
105
STATUS.md
105
STATUS.md
@@ -1,6 +1,6 @@
|
|||||||
# EmailProfiler - Current Implementation Status
|
# EmailProfiler - Current Implementation Status
|
||||||
|
|
||||||
**Last Updated**: 2026-07-07
|
**Last Updated**: 2026-07-14
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -48,10 +48,55 @@
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 🚧 IN PROGRESS (Phase 2: Application Layer - 5%)
|
## ✅ COMPLETED (Phase 2: Application Layer - 100%)
|
||||||
|
|
||||||
### DTOs
|
### DTOs (Common/Dtos/{Entity}/)
|
||||||
- ✅ `CommonDtos.cs` - EmailProfileDto, EmailAccountDto, EmailHistoryDto, EmailAttachmentDto
|
- ✅ `EmailProfiles/EmailProfileDto.cs`
|
||||||
|
- ✅ `EmailAccounts/EmailAccountDto.cs`
|
||||||
|
- ✅ `EmailHistories/EmailHistoryDto.cs`, `CreateEmailHistoryDto.cs`, `UpdateEmailHistoryStatusDto.cs`
|
||||||
|
- ✅ `EmailAttachments/EmailAttachmentDto.cs`, `CreateEmailAttachmentDto.cs`, `UpdateEmailAttachmentStatusDto.cs`
|
||||||
|
|
||||||
|
### Repository Interfaces (Generic Pattern - NO UnitOfWork)
|
||||||
|
- ✅ `IRepository<T>` - Generic repository with CreateAsync<TDto>, UpdateSingleAsync<TDto>, DeleteSingleAsync, UpdateAsync, DeleteAsync
|
||||||
|
|
||||||
|
### Service Interfaces
|
||||||
|
- ✅ `IEmailService.cs` - IMAP/SMTP operations
|
||||||
|
- ✅ `IPdfProcessingService.cs` - PDF validation and extraction
|
||||||
|
- ✅ `IDmsService.cs` - windream DMS integration
|
||||||
|
- ✅ `IEncryptionService.cs` - Password encryption
|
||||||
|
- ✅ `IEmailQueue.cs` - Email queue operations
|
||||||
|
- ✅ `ICommandPublisher.cs` - RabbitMQ command publishing
|
||||||
|
|
||||||
|
### MediatR Commands (Features/*/Commands/)
|
||||||
|
- ✅ `CreateEmailProfileCommand.cs` + Handler
|
||||||
|
- ✅ `UpdateEmailProfileCommand.cs` + Handler
|
||||||
|
- ✅ `DeleteEmailProfileCommand.cs` + Handler
|
||||||
|
- ✅ `CreateEmailAccountCommand.cs` + Handler
|
||||||
|
- ✅ `ProcessEmailCommand.cs` + Handler
|
||||||
|
|
||||||
|
### MediatR Queries (Features/*/Queries/)
|
||||||
|
- ✅ `GetEmailProfilesQuery.cs` + Handler
|
||||||
|
- ✅ `GetEmailProfileByIdQuery.cs` + Handler
|
||||||
|
- ✅ `GetActiveEmailProfilesQuery.cs` + Handler
|
||||||
|
- ✅ `GetEmailAccountsQuery.cs` + Handler
|
||||||
|
- ✅ `GetEmailAccountByIdQuery.cs` + Handler
|
||||||
|
- ✅ `GetEmailHistoryByProfileQuery.cs` + Handler (with pagination)
|
||||||
|
- ✅ `GetEmailHistoryByIdQuery.cs` + Handler
|
||||||
|
|
||||||
|
### Validators (Features/*/Validators/)
|
||||||
|
- ✅ `CreateEmailProfileCommandValidator.cs`
|
||||||
|
- ✅ `UpdateEmailProfileCommandValidator.cs`
|
||||||
|
- ✅ `CreateEmailAccountCommandValidator.cs` (conditional OAuth2/password validation)
|
||||||
|
- ✅ `ProcessEmailCommandValidator.cs` (with attachment validation)
|
||||||
|
|
||||||
|
### AutoMapper Profiles (Common/Mappings/)
|
||||||
|
- ✅ `EmailProfileMappingProfile.cs` (Command→Entity, DTO→Entity, Entity→DTO)
|
||||||
|
- ✅ `EmailAccountMappingProfile.cs`
|
||||||
|
- ✅ `EmailHistoryMappingProfile.cs`
|
||||||
|
- ✅ `EmailAttachmentMappingProfile.cs`
|
||||||
|
|
||||||
|
### DI Configuration
|
||||||
|
- ✅ `DependencyInjection.cs` - Registers MediatR, AutoMapper, FluentValidation
|
||||||
|
|
||||||
### NuGet Packages
|
### NuGet Packages
|
||||||
- ✅ Application project has:
|
- ✅ Application project has:
|
||||||
@@ -61,51 +106,23 @@
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## ❌ TODO (Phase 2: Application Layer - 95%)
|
## 🚧 IN PROGRESS (Phase 3: Infrastructure Layer - 15%)
|
||||||
|
|
||||||
### Repository Interfaces
|
### RabbitMQ Command Bus (COMPLETED)
|
||||||
- ❌ `IEmailProfileRepository.cs`
|
- ✅ `RabbitMqConfiguration.cs` - Configuration model
|
||||||
- ❌ `IEmailAccountRepository.cs`
|
- ✅ `RabbitMqCommandPublisher.cs` - ICommandPublisher implementation
|
||||||
- ❌ `IEmailHistoryRepository.cs`
|
- ✅ `RabbitMqCommandConsumer.cs` - BackgroundService for command consumption
|
||||||
- ❌ `IEmailProcessRepository.cs`
|
- ✅ `DependencyInjection.cs` - Infrastructure DI with RabbitMQ registration
|
||||||
- ❌ `IEmailOutboxRepository.cs`
|
- ✅ Configuration in `appsettings.json` (Server: 172.24.12.56:5672)
|
||||||
|
|
||||||
### Service Interfaces
|
### NuGet Packages (Partial)
|
||||||
- ❌ `IEmailService.cs`
|
- ✅ RabbitMQ.Client 7.2.1
|
||||||
- ❌ `IPdfProcessingService.cs`
|
- ✅ Microsoft.Extensions.Hosting 10.0.9
|
||||||
- ❌ `IDmsService.cs`
|
- ✅ Microsoft.Extensions.Options.ConfigurationExtensions 10.0.9
|
||||||
- ❌ `IEncryptionService.cs`
|
|
||||||
- ❌ `IEmailQueue.cs`
|
|
||||||
|
|
||||||
### MediatR Commands
|
|
||||||
- ❌ `CreateEmailProfileCommand.cs`
|
|
||||||
- ❌ `UpdateEmailProfileCommand.cs`
|
|
||||||
- ❌ `DeleteEmailProfileCommand.cs`
|
|
||||||
- ❌ `ActivateProfileCommand.cs`
|
|
||||||
- ❌ `DeactivateProfileCommand.cs`
|
|
||||||
- ❌ Similar commands for EmailAccount, EmailProcess, etc.
|
|
||||||
|
|
||||||
### MediatR Queries
|
|
||||||
- ❌ `GetEmailProfilesQuery.cs`
|
|
||||||
- ❌ `GetEmailProfileByIdQuery.cs`
|
|
||||||
- ❌ `GetActiveProfilesQuery.cs`
|
|
||||||
- ❌ `GetProfilesDueForPollingQuery.cs`
|
|
||||||
- ❌ Similar queries for other entities
|
|
||||||
|
|
||||||
### Validators
|
|
||||||
- ❌ `CreateEmailProfileCommandValidator.cs`
|
|
||||||
- ❌ `UpdateEmailProfileCommandValidator.cs`
|
|
||||||
- ❌ Similar validators for all commands
|
|
||||||
|
|
||||||
### Mappings
|
|
||||||
- ❌ `MappingProfile.cs` - AutoMapper configuration
|
|
||||||
|
|
||||||
### DI Configuration
|
|
||||||
- ❌ `DependencyInjection.cs` - Application layer DI setup
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## ❌ TODO (Phase 3: Infrastructure Layer - 0%)
|
## ❌ TODO (Phase 3: Infrastructure Layer - 85%)
|
||||||
|
|
||||||
### NuGet Packages
|
### NuGet Packages
|
||||||
- ❌ Microsoft.EntityFrameworkCore.SqlServer
|
- ❌ Microsoft.EntityFrameworkCore.SqlServer
|
||||||
|
|||||||
Reference in New Issue
Block a user