Modello CQRS con MediatR in ASP.NET Core

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.

Implementazione di CQRS con MediatR in ASP.NET Core 8

Iniziamo ad implementare CQRS utilizzando MediatR in un’applicazione ASP.NET Core 8:

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<string> 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<List<string>>;
				
			
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<GetUserNameQuery>
{
    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<List<string>>
{
    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<AddUserNameCommand, List<string>>
{

    public Task<List<string>> 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<IActionResult> List()
    {
        return Ok(await sender.Send(new GetUserNameQuery()));
    }
    
    [HttpPost]
    public async Task<IActionResult> 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.

Lascia un commento

Il tuo indirizzo email non sarà pubblicato. I campi obbligatori sono contrassegnati *

Skip to content