Extensii și pattern-uri suplimentare în CQRS
După ce o arhitectură CQRS este stabilă, următorul pas natural este rafinarea ei prin extensii și pattern-uri care o fac mai robustă, mai testabilă și mai ușor de întreținut. În această etapă, ne concentrăm pe trei direcții-cheie: decoratori peste MediatR, event publishing și audit logging. Toate acestea adresează nevoi comune ale aplicațiilor enterprise, fără să compromită separarea clară dintre comenzi și interogări.
🎯 1. Decorators peste MediatR pentru Logging, Retry și Validation
Unul dintre cele mai elegante moduri de a introduce comportamente comune în pipeline-ul aplicației este prin decoratori. În ecosistemul MediatR, acest lucru se realizează prin implementarea interfeței IPipelineBehavior<TRequest, TResponse>.
Această interfață acționează ca un middleware care interceptează toate cererile trimise către MediatR, permițând adăugarea logicilor cross-cutting – adică funcționalități care traversează mai multe componente, precum:
-
Logging – urmărirea cererilor și răspunsurilor pentru depanare și monitorizare;
-
Retry – reexecutarea unei comenzi în caz de erori tranzitorii;
-
Validation – validarea cererilor înainte de procesare.
Exemplu simplu de decorator pentru logging:
public class LoggingBehavior<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse>
{
private readonly ILogger<LoggingBehavior<TRequest, TResponse>> _logger;
public LoggingBehavior(ILogger<LoggingBehavior<TRequest, TResponse>> logger)
=> _logger = logger;
public async Task<TResponse> Handle(
TRequest request,
RequestHandlerDelegate<TResponse> next,
CancellationToken cancellationToken)
{
_logger.LogInformation("Handling {Request}", typeof(TRequest).Name);
var response = await next();
_logger.LogInformation("Handled {Request}", typeof(TRequest).Name);
return response;
}
}
Această abordare permite adăugarea de comportamente fără a „polua” handler-ele individuale. Cu un simplu services.AddTransient(typeof(IPipelineBehavior<,>), typeof(LoggingBehavior<,>));, toate comenzile și interogările beneficiază automat de logging centralizat.
📢 2. Event Publishing: Domain Events vs Integration Events
Evenimentele sunt o componentă naturală a modelului Domain-Driven Design, iar în contextul CQRS, ele capătă un rol major. În esență, vorbim despre două categorii:
-
Domain Events – reprezintă ceva ce s-a întâmplat în interiorul domeniului. De exemplu, PostCreatedDomainEvent poate fi declanșat atunci când o postare nouă este creată.
-
Integration Events – sunt utilizate pentru a notifica alte sisteme externe sau microservicii despre o schimbare. Ele traversează limitele aplicației.
Pentru Domain Events, MediatR oferă o integrare elegantă prin interfața INotification și handler-ele asociate INotificationHandler<TNotification>.
public class PostCreatedDomainEvent : INotification
{
public Guid PostId { get; }
public string Title { get; }
public PostCreatedDomainEvent(Guid postId, string title)
{
PostId = postId;
Title = title;
}
}
public class PostCreatedEventHandler : INotificationHandler<PostCreatedDomainEvent>
{
private readonly ILogger<PostCreatedEventHandler> _logger;
public PostCreatedEventHandler(ILogger<PostCreatedEventHandler> logger) => _logger = logger;
public Task Handle(PostCreatedDomainEvent notification, CancellationToken cancellationToken)
{
_logger.LogInformation("New post created: {Title}", notification.Title);
return Task.CompletedTask;
}
}
Astfel, domeniul rămâne coerent, iar logica de reacție la evenimente poate fi extinsă fără a afecta nucleul aplicației.
Pentru Integration Events, este recomandată o abordare asincronă – de exemplu, publicarea în RabbitMQ, Azure Service Bus sau Kafka – pentru a decupla complet comunicația între aplicații.
🧾 3. Audit Logging la comenzi
În multe aplicații enterprise, urmărirea acțiunilor utilizatorilor este o cerință obligatorie, mai ales în medii financiare, medicale sau guvernamentale. În CQRS, audit logging-ul poate fi implementat elegant tot printr-un pipeline behavior sau direct în handler-ele de comandă.
O abordare simplă presupune crearea unei entități AuditLog și salvarea evenimentelor într-o tabelă auxiliară sau într-un Event Store dedicat.
public class AuditBehavior<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse>
{
private readonly IAuditService _auditService;
public AuditBehavior(IAuditService auditService)
=> _auditService = auditService;
public async Task<TResponse> Handle(
TRequest request,
RequestHandlerDelegate<TResponse> next,
CancellationToken cancellationToken)
{
var response = await next();
await _auditService.SaveAsync(request, response);
return response;
}
}
Prin această metodă, fiecare comandă trimisă prin MediatR lasă o urmă în baza de date – cu detalii despre cine a făcut acțiunea, când și ce s-a modificat.
Această transparență ajută la depanare, securitate și conformitate cu standarde precum GDPR sau ISO 27001.
🔚 Concluzie
Extensiile și pattern-urile din această etapă duc arhitectura CQRS la un nivel matur.
Prin decoratori, evenimente și audit logging, aplicația devine:
-
Scalabilă – logica comună este centralizată;
-
Observabilă – toate acțiunile pot fi urmărite și analizate;
-
Ușor de extins – adăugarea unui nou comportament global nu afectează handler-ele existente.
CQRS nu înseamnă doar separarea dintre comenzi și interogări, ci și crearea unui cadru solid care facilitează mentenanța, auditul și extensibilitatea pe termen lung.