Nel mondo dello sviluppo software, creare applicazioni che siano facili da mantenere, scalabili ed efficienti è una priorità assoluta. Per raggiungere questo obiettivo, gli sviluppatori utilizzano vari modelli di progettazione e librerie. Una combinazione che ha guadagnato popolarità è il modello CQRS (Command Query Responsibility Segregation) con MediatR in ASP.NET Core, utilizzando il linguaggio di programmazione C#.
Introduzione CQRS e MediatR
CQRS (Command Query Responsibility Segregation) e MediatR sono due concetti chiave nell’ecosistema di sviluppo ASP.NET Core.
CQRS
Il CQRS, o Command Query Responsibility Segregation, è un modello architetturale che separa il modello di dominio in due parti distinte: una per la scrittura dei dati (comandi) e l’altra per la lettura dei dati (query). Questa separazione consente una maggiore flessibilità nel design dell’applicazione e una migliore gestione delle richieste.
MediatR
MediatR è una libreria che semplifica l’implementazione del pattern CQRS, fornendo un sistema di mediatori che facilita la distribuzione dei comandi e delle query all’interno dell’applicazione.
1. Progetto Web API
Per iniziare l’implementazione, creiamo una nuova applicazione Web API
2. Installazione e configurazione MediatR
Una volta creato il progetto, procediamo con l’installazione del pacchetto NuGet di MediatR e la sua configurazione.
Installazione MediatR
Per installare il pacchetto di MediatR, è possibile eseguire il seguente comando oppure navigare nella gestione dei pacchetti NuGet della soluzione appena creata.
dotnet add package MediatR
Configurazione MediatR
Prima di procedere con la configurazione del servizio, è importante controllare la versione installata di MediatR. Nel nostro caso, stiamo utilizzando la versione 12, quindi possiamo procedere utilizzando:
builder.Services.AddMediatR(cfg => cfg.RegisterServicesFromAssemblies(AppDomain.CurrentDomain.GetAssemblies()));
Per le versioni precedenti alla 12, è necessario registrare MediatR utilizzando:
builder.Services.AddMediatR(typeof(Startup).Assembly);
3. Implementazione del modello CQRS
Per organizzare al meglio il nostro progetto, consiglio di suddividere le funzionalità in ‘features‘, così da ottenere una struttura ordinata delle cartelle, ad esempio ‘Features -> NomeFeature‘. All’interno di ciascuna feature, definiremmo sia il comando/query che il relativo handler.
Emulazione di un database
Per emulare un database dobbiamo creare una classe statica che utilizzeremo per restituire la lista degli utenti e per aggiungerne di nuovi. È importante notare che i dati inseriti all’interno della classe statica verranno persi una volta che l’applicazione viene interrotta.
namespace CQRSExample.NET.Database;
public static class FakeDatabase
{
public static List UserNames =
[
"User123",
"TestUser",
"GuestUser",
"DemoUser",
"SampleUser"
];
}
Feature lettura
Per semplicità, definiremo la nostra feature di lettura come la lettura di una lista di utenti.
Query lettura
using MediatR;
namespace CQRSExample.NET.Features.GetUsernames;
public class GetUsernameQuery : IRequest>;
Handler Query Lettura
Una volta definita la query, procediamo con l’implementazione della logica di lettura degli utenti. Successivamente, associamo la query precedentemente creata all’handler che verrà creato.
using CQRSExample.NET.Database;
using MediatR;
namespace CQRSExample.NET.Features.GetUserName;
public class GetUserNameHandler : IRequestHandler
{
public Task Handle(GetUserNameQuery request, CancellationToken cancellationToken)
{
return Task.FromResult(FakeDatabase.UserNames);
}
}
Feature aggiungi nome utente
Ora che abbiamo definito la nostra feature per la lettura, possiamo procedere con la definizione della feature che gestirà l’aggiunta di nuovi utenti nel nostro database temporaneo.
Command aggiungi nome utente
using MediatR;
namespace CQRSExample.NET.Features.AddUserName;
public class AddUserNameCommand : IRequest>
{
public string UserName { get; set; }
}
Handler Command aggiungi nome utente
Come nel passaggio precedente della lettura degli utenti, una volta definito il command per l’aggiunta di nuovi utenti, dobbiamo procedere con l’implementazione della logica nel relativo handler.
using CQRSExample.NET.Database;
using MediatR;
namespace CQRSExample.NET.Features.AddUserName;
public class AddUserNameHandler : IRequestHandler>
{
public Task> Handle(AddUserNameCommand request, CancellationToken cancellationToken)
{
FakeDatabase.UserNames.Add(request.UserName);
return Task.FromResult(FakeDatabase.UserNames);
}
}
4. Controller per la gestione delle richieste API
Adesso che abbiamo implementato le nostre feature, possiamo procedere con l’implementazione di un controller per gestire le richieste HTTP e controllare il corretto funzionamento delle nostre features utilizzando Swagger UI.
1. Creazione Controller
Per definire il controller, possiamo utilizzare il seguente codice:
using CQRSExample.NET.Features.AddUserName;
using CQRSExample.NET.Features.GetUserName;
using MediatR;
using Microsoft.AspNetCore.Mvc;
namespace CQRSExample.NET.Controllers;
[Route("/[controller]/[action]")]
public class UserNameController(ISender sender) : ControllerBase
{
[HttpGet]
public async Task List()
{
return Ok(await sender.Send(new GetUserNameQuery()));
}
[HttpPost]
public async Task Add(string username)
{
return Ok(await sender.Send(new AddUserNameCommand
{
UserName = username
}));
}
}
2. Registrazione controllers
Ricordo che a partire da ASP.NET Core 8, la registrazione del servizio dei controller e la loro mappatura non viene più implementata di default. Di conseguenza, è necessario configurare lo startup dell’applicazione andando in Program.cs
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
builder.Services.AddControllers(); // Registrazione del servizio che si occupa dei controllers
builder.Services.AddMediatR(cfg => cfg.RegisterServicesFromAssemblies(AppDomain.CurrentDomain.GetAssemblies()));
var app = builder.Build();
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
app.UseHttpsRedirection();
app.MapControllers(); // Mappatura dei controllers
app.Run();
5. Avvio dell’applicazione e test delle features con Swagger
Siamo arrivati alla fine! Ora possiamo avviare la nostra applicazione e provare le API utilizzando Swagger.Una volta avviata l’applicazione, apri il browser e visita l’URL del tuo servizio seguito da /swagger
, ad esempio http://localhost:5000/swagger
. Questo ti porterà all’interfaccia di Swagger.