Clean Architecture in .NET 2026: CQRS, MediatR e Domande per Colloqui Tecnici
Una guida completa alla Clean Architecture in .NET con CQRS e MediatR. Include best practice, esempi di codice e domande frequenti nei colloqui per architetti .NET.

La Clean Architecture si è affermata come pattern architetturale di riferimento per le applicazioni .NET moderne. Combinata con CQRS (Command Query Responsibility Segregation) e MediatR, consente di creare sistemi manutenibili, testabili e scalabili. Questo articolo esplora l'implementazione pratica di questi pattern in .NET 9 e prepara ai colloqui tecnici per sviluppatori senior.
La Clean Architecture separa la logica di business dai dettagli tecnici come database e framework. Questa separazione permette test indipendenti e facilita notevolmente i futuri cambi tecnologici.
Che cos'è la Clean Architecture?
La Clean Architecture, conosciuta anche come Onion Architecture o Hexagonal Architecture, organizza il codice in strati concentrici. Lo strato più interno contiene la logica di dominio, mentre gli strati esterni gestiscono infrastruttura e presentazione.
I principi fondamentali includono:
- Indipendenza dai framework: La logica di business non conosce i framework
- Testabilità: Le regole di business possono essere testate senza UI, database o servizi esterni
- Indipendenza dalla UI: L'interfaccia utente può essere sostituita senza modificare le regole di business
- Indipendenza dal database: Oracle, SQL Server o MongoDB sono intercambiabili
Struttura del Progetto in .NET
Una tipica soluzione Clean Architecture in .NET consiste in quattro progetti principali:
// Struttura del progetto
MyApp.Domain/ // Entità, Value Objects, Domain Events
MyApp.Application/ // Use Cases, Commands, Queries, Interfaces
MyApp.Infrastructure/ // Accesso ai dati, servizi esterni
MyApp.WebApi/ // Controller, Middleware, configurazione DILa direzione delle dipendenze punta sempre verso l'interno. Infrastructure e WebApi referenziano Application, ma mai il contrario.
CQRS: Separare Commands e Queries
CQRS separa le operazioni di lettura e scrittura in modelli distinti. I Commands modificano lo stato, le Queries restituiscono dati senza effetti collaterali.
// Definizione Command
public record CreateProductCommand(
string Name,
decimal Price,
string Description
) : IRequest<ProductDto>;
// Definizione Query
public record GetProductByIdQuery(Guid Id) : IRequest<ProductDto?>;
// Command Handler
public class CreateProductCommandHandler
: IRequestHandler<CreateProductCommand, ProductDto>
{
private readonly IProductRepository _repository;
private readonly IUnitOfWork _unitOfWork;
public CreateProductCommandHandler(
IProductRepository repository,
IUnitOfWork unitOfWork)
{
_repository = repository;
_unitOfWork = unitOfWork;
}
public async Task<ProductDto> Handle(
CreateProductCommand request,
CancellationToken cancellationToken)
{
var product = new Product(
request.Name,
request.Price,
request.Description);
await _repository.AddAsync(product, cancellationToken);
await _unitOfWork.SaveChangesAsync(cancellationToken);
return product.ToDto();
}
}Configurazione di MediatR
MediatR implementa il pattern Mediator e disaccoppia i mittenti dai destinatari. La configurazione in .NET 9 avviene nel Program.cs:
var builder = WebApplication.CreateBuilder(args);
// Registrazione MediatR
builder.Services.AddMediatR(cfg => {
cfg.RegisterServicesFromAssembly(
typeof(CreateProductCommand).Assembly);
// Aggiunta Pipeline Behaviors
cfg.AddBehavior<ValidationBehavior<,>>();
cfg.AddBehavior<LoggingBehavior<,>>();
});
// FluentValidation per Commands
builder.Services.AddValidatorsFromAssembly(
typeof(CreateProductCommandValidator).Assembly);Pipeline Behaviors per Cross-Cutting Concerns
I Pipeline Behaviors permettono l'implementazione di aspetti trasversali come validazione, logging e caching:
public class ValidationBehavior<TRequest, TResponse>
: IPipelineBehavior<TRequest, TResponse>
where TRequest : IRequest<TResponse>
{
private readonly IEnumerable<IValidator<TRequest>> _validators;
public ValidationBehavior(
IEnumerable<IValidator<TRequest>> validators)
{
_validators = validators;
}
public async Task<TResponse> Handle(
TRequest request,
RequestHandlerDelegate<TResponse> next,
CancellationToken cancellationToken)
{
if (!_validators.Any())
return await next();
var context = new ValidationContext<TRequest>(request);
var validationResults = await Task.WhenAll(
_validators.Select(v =>
v.ValidateAsync(context, cancellationToken)));
var failures = validationResults
.SelectMany(r => r.Errors)
.Where(f => f != null)
.ToList();
if (failures.Count != 0)
throw new ValidationException(failures);
return await next();
}
}Domain Events con MediatR
I Domain Events segnalano eventi di business importanti e permettono reazioni debolmente accoppiate:
// Definizione Domain Event
public record ProductCreatedEvent(Guid ProductId, string Name)
: INotification;
// Event Handler
public class ProductCreatedEventHandler
: INotificationHandler<ProductCreatedEvent>
{
private readonly IEmailService _emailService;
private readonly ILogger<ProductCreatedEventHandler> _logger;
public ProductCreatedEventHandler(
IEmailService emailService,
ILogger<ProductCreatedEventHandler> logger)
{
_emailService = emailService;
_logger = logger;
}
public async Task Handle(
ProductCreatedEvent notification,
CancellationToken cancellationToken)
{
_logger.LogInformation(
"Prodotto creato: {ProductName}",
notification.Name);
await _emailService.SendProductNotificationAsync(
notification.ProductId,
cancellationToken);
}
}Repository Pattern e Unit of Work
Il Repository Pattern astrae l'accesso ai dati e rende il dominio indipendente dal layer di persistenza:
// Interface nell'Application Layer
public interface IProductRepository
{
Task<Product?> GetByIdAsync(
Guid id,
CancellationToken cancellationToken = default);
Task<IReadOnlyList<Product>> GetAllAsync(
CancellationToken cancellationToken = default);
Task AddAsync(
Product product,
CancellationToken cancellationToken = default);
void Update(Product product);
void Delete(Product product);
}
// Implementazione nell'Infrastructure Layer
public class ProductRepository : IProductRepository
{
private readonly ApplicationDbContext _context;
public ProductRepository(ApplicationDbContext context)
{
_context = context;
}
public async Task<Product?> GetByIdAsync(
Guid id,
CancellationToken cancellationToken = default)
{
return await _context.Products
.Include(p => p.Category)
.FirstOrDefaultAsync(
p => p.Id == id,
cancellationToken);
}
public async Task AddAsync(
Product product,
CancellationToken cancellationToken = default)
{
await _context.Products.AddAsync(product, cancellationToken);
}
}Minimal APIs con MediatR
.NET 9 permette API Minimal eleganti in combinazione con MediatR:
// Definizione Endpoint
app.MapPost("/api/products", async (
CreateProductCommand command,
ISender sender,
CancellationToken cancellationToken) =>
{
var result = await sender.Send(command, cancellationToken);
return Results.Created($"/api/products/{result.Id}", result);
})
.WithName("CreateProduct")
.WithOpenApi();
app.MapGet("/api/products/{id:guid}", async (
Guid id,
ISender sender,
CancellationToken cancellationToken) =>
{
var result = await sender.Send(
new GetProductByIdQuery(id),
cancellationToken);
return result is not null
? Results.Ok(result)
: Results.NotFound();
})
.WithName("GetProductById")
.WithOpenApi();Pronto a superare i tuoi colloqui su .NET?
Pratica con i nostri simulatori interattivi, flashcards e test tecnici.
Unit Testing con Clean Architecture
La separazione in layer semplifica notevolmente i test:
public class CreateProductCommandHandlerTests
{
private readonly Mock<IProductRepository> _repositoryMock;
private readonly Mock<IUnitOfWork> _unitOfWorkMock;
private readonly CreateProductCommandHandler _handler;
public CreateProductCommandHandlerTests()
{
_repositoryMock = new Mock<IProductRepository>();
_unitOfWorkMock = new Mock<IUnitOfWork>();
_handler = new CreateProductCommandHandler(
_repositoryMock.Object,
_unitOfWorkMock.Object);
}
[Fact]
public async Task Handle_ValidCommand_CreatesProduct()
{
// Arrange
var command = new CreateProductCommand(
"Prodotto Test",
99.99m,
"Descrizione");
// Act
var result = await _handler.Handle(
command,
CancellationToken.None);
// Assert
Assert.NotNull(result);
Assert.Equal(command.Name, result.Name);
_repositoryMock.Verify(
r => r.AddAsync(
It.IsAny<Product>(),
It.IsAny<CancellationToken>()),
Times.Once);
_unitOfWorkMock.Verify(
u => u.SaveChangesAsync(It.IsAny<CancellationToken>()),
Times.Once);
}
}Domande Frequenti nei Colloqui sulla Clean Architecture
Domanda 1: Qual è la differenza tra Clean Architecture e architettura N-Tier tradizionale?
Nelle architetture N-Tier, le dipendenze puntano dall'alto verso il basso: Presentation → Business → Data Access. La Clean Architecture inverte questa dipendenza: il Dominio sta al centro e non ha dipendenze verso gli strati esterni. L'Infrastructure implementa interfacce definite nell'Application Layer.
Domanda 2: Quando utilizzare CQRS?
CQRS è adatto per sistemi con requisiti di lettura e scrittura diversi. Casi d'uso tipici includono: elevate richieste di lettura con scritture rare, logica di dominio complessa nelle operazioni di scrittura, o quando servono modelli di lettura diversi per consumatori differenti.
Domanda 3: Come prevenire dipendenze circolari tra i layer?
Attraverso l'applicazione coerente del Dependency Inversion Principle: le interfacce sono definite nel layer interno (Application), le implementazioni nel layer esterno (Infrastructure). La Dependency Injection configura l'associazione a runtime.
Domanda 4: Quali sono gli svantaggi della Clean Architecture?
Maggiore sforzo iniziale, più codice boilerplate e complessità aumentata per progetti piccoli. I vantaggi emergono solo in applicazioni medio-grandi con prospettive di manutenzione a lungo termine.
Domanda 5: Come gestire i Domain Events in sistemi distribuiti?
Per i sistemi distribuiti, MediatR non è sufficiente. Gli Integration Events vengono distribuiti tramite message broker come RabbitMQ, Azure Service Bus o Kafka. L'Outbox Pattern garantisce la consistenza tra operazioni di database e pubblicazione degli eventi.
Best Practice per il 2026
-
Vertical Slices in combinazione con Clean Architecture: Le feature sono organizzate come unità autonome, mantenendo la separazione in layer all'interno di ogni slice.
-
Result Pattern invece di Exceptions: Commands e Queries restituiscono oggetti Result che modellano esplicitamente successo o fallimento.
-
Source Generators per il boilerplate: I .NET Source Generators riducono il codice ripetitivo per mapping e validazioni.
-
Aspire per lo sviluppo Cloud-Native: .NET Aspire semplifica l'orchestrazione di applicazioni Clean Architecture in ambienti containerizzati.
Conclusione
La Clean Architecture con CQRS e MediatR costituisce una base solida per applicazioni .NET scalabili. La chiara separazione delle responsabilità facilita manutenzione, testing ed evoluzione del software. Per i colloqui tecnici, la comprensione dei principi sottostanti è importante quanto l'implementazione pratica. L'investimento in un'architettura pulita ripaga nel lungo termine attraverso una ridotta complessità e una maggiore produttività degli sviluppatori.
Sapresti trovare il bug in .NET?
Uno snippet reale, un bug nascosto, un tentativo al giorno. Senza account per provare.

Scritto da
Anthony Fillion-MailletFondatore di SharpSkill
Sviluppatore fullstack da oltre 10 anni. Guida SharpSkill e risponde di tutto ciò che vi viene pubblicato.
Aggiornato il 15 settembre 2026
Tag
Condividi
Articoli correlati

Clean Code Architecture C#: Guida Completa e Domande per Colloqui 2026
Guida completa alla Clean Code Architecture in C# con principi SOLID, architettura a strati e le domande più frequenti nei colloqui tecnici per sviluppatori .NET.

.NET 10 nel 2026: Nuove Funzionalita, Native AOT e C# 14 per la Preparazione ai Colloqui
.NET 10 viene rilasciato come versione Long-Term Support con miglioramenti Native AOT, extension members di C# 14, la keyword field e app basate su file. Una guida completa sulle nuove funzionalita, i guadagni prestazionali e le conoscenze pronte per i colloqui per sviluppatori .NET nel 2026.

.NET MAUI nel 2026: Sviluppo Cross-Platform e Domande di Colloquio
Tutorial .NET MAUI per il 2026: sviluppare app cross-platform con .NET 10, handlers, MVVM, HybridWebView. Include le domande di colloquio.