Le applicazioni si vogliono semplici da fuori e finiscono complesse dentro. Più API si incrociano, più classi devono chiamarsi a vicenda, più il codice del back-end diventa un grafo di dipendenze impossibile da seguire. Il pattern Mediator è uno dei modi più puliti per tenerlo sotto controllo.
Cos'è il pattern Mediator
È un design pattern che riduce le comunicazioni dirette fra oggetti. Invece di avere N classi che si chiamano fra loro, hai un mediatore al centro: ognuno parla solo con lui, lui smista. Risultato: accoppiamento basso, refactoring più semplici.
Cosa ti dà
- Accoppiamento debole tra le classi
- Codice più manutenibile e riusabile
- Niente più referenze circolari
Quando ha senso usarlo
Mediator non è un must per ogni progetto. Diventa utile quando:
- Più classi che devono parlarsi senza conoscersi a vicenda
- Vuoi spezzare le dipendenze strette
- Le interazioni tra classi cambiano spesso
Per un CRUD minimale, è overkill. Per un dominio complesso con regole di business non banali, ti salva il debito tecnico.
Implementazione con MediatR
Il codice qui sotto definisce due tipi di richieste con MediatR: un comando per aggiornare il nome utente, una query per leggerlo. Avere comandi e query separati è l'idea di base di CQRS (Command Query Responsibility Segregation): scrivere e leggere sono operazioni diverse, scalabili in modo diverso, e tenerle separate aiuta sul lungo periodo.
using MediatR;
public class UpdateUserNameCommand : IRequest<bool>
{
public Guid UserId { get; }
public string NewUserName { get; }
public UpdateUserNameCommand(Guid userId, string newUserName)
{
UserId = userId;
NewUserName = newUserName;
}
}
public class GetUserNameQuery : IRequest<string>
{
public Guid UserId { get; }
public GetUserNameQuery(Guid userId)
{
UserId = userId;
}
}Gli handler che eseguono effettivamente la logica:
using MediatR;
using Microsoft.Extensions.Logging;
public class UpdateUserNameCommandHandler : IRequestHandler<UpdateUserNameCommand, bool>
{
private readonly ILogger<UpdateUserNameCommandHandler> _logger;
public UpdateUserNameCommandHandler(ILogger<UpdateUserNameCommandHandler> logger)
{
_logger = logger;
}
public async Task<bool> Handle(UpdateUserNameCommand request, CancellationToken cancellationToken)
{
_logger.LogInformation($"Updating user {request.UserId} to {request.NewUserName}");
return true; // Simulazione di successo
}
}Handler della Query
public class GetUserNameQueryHandler : IRequestHandler<GetUserNameQuery, string>
{
private readonly ILogger<GetUserNameQueryHandler> _logger;
public GetUserNameQueryHandler(ILogger<GetUserNameQueryHandler> logger)
{
_logger = logger;
}
public async Task<string> Handle(GetUserNameQuery request, CancellationToken cancellationToken)
{
_logger.LogInformation($"Fetching user name for user {request.UserId}");
return "User Name"; // Simulazione di un nome utente
}
}MediatR è la libreria .NET di riferimento per il pattern. In progetti complessi rende il codice più facile da seguire perché ogni intent (comando o query) ha il suo handler isolato.
Comandi e query con i rispettivi handler: ogni operazione vive nel suo file, le responsabilità sono chiare, e gli unit test diventano banali da scrivere perché ogni handler ha una dipendenza singola e una responsabilità sola.


