Clean Architecture in ASP.NET Core 10
Clean Architecture in ASP.NET Core 10
Indice – Clean Architecture in ASP.NET Core 10
- Introduzione alla Clean Architecture
- Il contesto storico: dall’architettura client-server alla Clean Architecture
- I principi fondamentali della Clean Architecture
- La struttura dei progetti in ASP.NET Core 10
- Il progetto Core: il cuore dell’architettura
- Il progetto Use Cases: implementare CQRS
- Il progetto Infrastructure: gestire le dipendenze esterne
- Il progetto Web: API endpoints e presentazione
- Dependency Injection in ASP.NET Core 10
- Pattern Repository e Specification
- Testing e testabilità
- Template e strumenti pratici
- Vertical Slice Architecture vs Clean Architecture
- Conclusioni: l’importanza della formazione continua
1. Introduzione alla Clean Architecture
La Clean Architecture rappresenta uno degli approcci architetturali più consolidati per lo sviluppo di applicazioni enterprise in ASP.NET Core. Introdotta da Robert C. Martin (Uncle Bob) nel 2012 e successivamente approfondita nel libro omonimo del 2017, questa architettura non è altro che l’ultima evoluzione di pattern già noti come Hexagonal Architecture, Ports and Adapters e Onion Architecture.
L’obiettivo principale della Clean Architecture è separare la logica di business dalle preoccupazioni infrastrutturali, rendendo il codice più testabile, manutenibile e indipendente da framework specifici. In ASP.NET Core 10, questi principi trovano una naturale implementazione grazie al supporto nativo per Dependency Injection e all’ecosistema maturo di librerie disponibili.
La prima legge dell’architettura software, come ci ricorda il libro “Fundamentals of Software Architecture”, afferma che tutto è un trade-off. Non esiste un’architettura perfetta per ogni scenario: la scelta dipende dal contesto, dalla dimensione del team, dalla complessità del dominio e dai requisiti non funzionali del progetto.
2. Il contesto storico: dall’architettura client-server alla Clean Architecture
Per comprendere appieno il valore della Clean Architecture, è utile ripercorrere l’evoluzione delle architetture software negli ultimi decenni. Negli anni ’90, l’approccio dominante era l’architettura client-server, dove le applicazioni comunicavano direttamente con un database server condiviso. Questo modello creava problemi di accoppiamento: modificare lo schema del database richiedeva cambiamenti in tutte le applicazioni dipendenti.
Con l’introduzione della layered architecture (architettura a livelli) nei primi anni 2000, si è tentato di separare UI, business logic e data access in progetti distinti. Tuttavia, il flusso delle dipendenze continuava a puntare verso il database, rendendo difficile testare la logica di business in isolamento.
L’avvento del Test-Driven Development, promosso da Kent Beck nel 2002, e del Domain-Driven Design, introdotto da Eric Evans nel 2003, ha portato alla necessità di architetture più disaccoppiate. La Hexagonal Architecture di Alistair Cockburn (2005) e la Onion Architecture di Jeffrey Palermo hanno posto le basi per quella che sarebbe diventata la Clean Architecture.
3. I principi fondamentali della Clean Architecture
La Clean Architecture si basa su un principio cardine: la Dependency Rule. Questa regola stabilisce che le dipendenze devono fluire verso l’interno, verso il core dell’applicazione dove risiede il domain model, e mai verso l’esterno, verso l’infrastruttura.
In termini pratici, significa che:
- Il Domain Layer non dipende da nulla
- L’Application Layer dipende solo dal Domain
- L’Infrastructure Layer dipende da Application e Domain
- Il Presentation Layer dipende da Application (ma non da Infrastructure)
Questa inversione delle dipendenze è possibile grazie al Dependency Inversion Principle (DIP), uno dei principi SOLID. Le interfacce vengono definite nei layer interni, mentre le implementazioni concrete risiedono nei layer esterni.
// Nel progetto Core - Interfaccia
public interface IRepository<T> where T : IAggregateRoot
{
Task<T?> GetByIdAsync(int id);
Task<List<T>> ListAsync();
}
// Nel progetto Infrastructure - Implementazione
public class EfRepository<T> : IRepository<T> where T : class, IAggregateRoot
{
private readonly AppDbContext _dbContext;
public EfRepository(AppDbContext dbContext)
{
_dbContext = dbContext;
}
public async Task<T?> GetByIdAsync(int id)
{
return await _dbContext.Set<T>().FindAsync(id);
}
}
4. La struttura dei progetti in ASP.NET Core 10
Una soluzione Clean Architecture in ASP.NET Core 10 è tipicamente organizzata in quattro progetti principali: Core, UseCases, Infrastructure e Web. Questa separazione fisica tramite progetti .NET sfrutta il fatto che il compilatore impedisce riferimenti circolari, garantendo l’applicazione della Dependency Rule.
La struttura base è:
- Core: contiene entities, value objects, aggregates, domain events e interfacce
- UseCases: implementa i casi d’uso con pattern CQRS (Commands e Queries)
- Infrastructure: contiene l’accesso ai dati, servizi esterni, email provider
- Web: espone API endpoints e gestisce le richieste HTTP
Per progetti più semplici o MVP, esiste anche un template “Minimal Clean Architecture” che organizza tutto in un singolo progetto con architettura a vertical slice.
5. Il progetto Core: il cuore dell’architettura
Il progetto Core rappresenta il cuore pulsante dell’applicazione. Qui risiede il domain model con tutte le regole di business, completamente isolato da preoccupazioni infrastrutturali.
Un esempio di entity nel progetto Core:
public class Contributor : EntityBase, IAggregateRoot
{
public ContributorName Name { get; private set; }
public ContributorEmail Email { get; private set; }
private readonly List<ContributorTask> _tasks = new();
public IReadOnlyCollection<ContributorTask> Tasks => _tasks.AsReadOnly();
public void UpdateName(ContributorName newName)
{
Name = newName;
RegisterDomainEvent(new ContributorNameUpdatedEvent(Id, newName));
}
public void AddTask(ContributorTask task)
{
Guard.Against.Null(task);
_tasks.Add(task);
}
}
Notare l’uso di:
- Value Objects (ContributorName, ContributorEmail) invece di primitive
- Domain Events per notificare cambiamenti di stato
- Encapsulation con setter privati e liste readonly
- IAggregateRoot marker interface per identificare aggregate roots
6. Il progetto Use Cases: implementare CQRS
Il progetto Use Cases è organizzato seguendo il pattern CQRS (Command Query Responsibility Segregation), separando operazioni di lettura e scrittura. I Commands modificano lo stato del sistema e utilizzano il repository pattern, mentre le Queries sono read-only e possono usare qualsiasi approccio efficiente.
Esempio di Command:
// Command
public record UpdateContributorCommand(int Id, string Name) : ICommand<Result>;
// Handler
public class UpdateContributorHandler : ICommandHandler<UpdateContributorCommand, Result>
{
private readonly IRepository<Contributor> _repository;
public UpdateContributorHandler(IRepository<Contributor> repository)
{
_repository = repository;
}
public async Task<Result> Handle(UpdateContributorCommand request, CancellationToken ct)
{
var contributor = await _repository.GetByIdAsync(request.Id);
if (contributor == null) return Result.NotFound();
contributor.UpdateName(new ContributorName(request.Name));
await _repository.SaveChangesAsync();
return Result.Success();
}
}
7. Il progetto Infrastructure: gestire le dipendenze esterne
Il progetto Infrastructure contiene tutte le implementazioni concrete delle interfacce definite in Core e UseCases. Qui troviamo:
- Data Access: implementazione con Entity Framework Core
- Email Services: invio email tramite SMTP
- External APIs: client HTTP per servizi esterni
- File System: accesso a file e storage
Esempio di configurazione Entity Framework:
public class ContributorConfiguration : IEntityTypeConfiguration<Contributor>
{
public void Configure(EntityTypeBuilder<Contributor> builder)
{
builder.Property(c => c.Name)
.HasConversion(
name => name.Value,
value => new ContributorName(value))
.HasMaxLength(100);
builder.Property(c => c.Email)
.HasVoggenConversion()
.HasMaxLength(255);
builder.HasMany(c => c.Tasks)
.WithOne()
.OnDelete(DeleteBehavior.Cascade);
}
}
8. Il progetto Web: API endpoints e presentazione
Il progetto Web in ASP.NET Core 10 utilizza FastEndpoints per implementare API seguendo il pattern REPR (Request-Endpoint-Response). Questo approccio offre vantaggi in termini di organizzazione e performance rispetto ai tradizionali Controller.
public class UpdateContributorEndpoint : Endpoint<UpdateContributorRequest, UpdateContributorResponse>
{
private readonly IMediator _mediator;
public UpdateContributorEndpoint(IMediator mediator)
{
_mediator = mediator;
}
public override void Configure()
{
Put("/contributors/{id}");
AllowAnonymous();
}
public override async Task HandleAsync(UpdateContributorRequest req, CancellationToken ct)
{
var command = new UpdateContributorCommand(req.Id, req.Name);
var result = await _mediator.Send(command, ct);
if (result.IsSuccess)
await SendOkAsync(new UpdateContributorResponse(result.Value), ct);
else
await SendNotFoundAsync(ct);
}
}
9. Dependency Injection in ASP.NET Core 10
ASP.NET Core include un service container integrato, IServiceProvider, che gestisce automaticamente la creazione e il ciclo di vita delle dipendenze. La registrazione dei servizi avviene nel file Program.cs.
I servizi possono essere registrati con tre lifetime differenti: Transient (creati ogni volta), Scoped (uno per richiesta HTTP) e Singleton (uno per l’intera applicazione).
var builder = WebApplication.CreateBuilder(args);
// Transient: nuova istanza ogni volta
builder.Services.AddTransient<IEmailSender, SmtpEmailSender>();
// Scoped: una istanza per richiesta HTTP
builder.Services.AddScoped<IRepository<Contributor>, EfRepository<Contributor>>();
// Singleton: una istanza per l'intera applicazione
builder.Services.AddSingleton<IConfiguration>(builder.Configuration);
// DbContext è automaticamente Scoped
builder.Services.AddDbContext<AppDbContext>(options =>
options.UseSqlServer(connectionString));
L’injection avviene tramite constructor injection, il metodo più sicuro e raccomandato:
public class ContributorService
{
private readonly IRepository<Contributor> _repository;
private readonly IEmailSender _emailSender;
public ContributorService(
IRepository<Contributor> repository,
IEmailSender emailSender)
{
_repository = repository;
_emailSender = emailSender;
}
}
10. Pattern Repository e Specification
Il pattern Repository astrae l’accesso ai dati, mentre il pattern Specification incapsula la logica di query riutilizzabile.
// Specification
public class ContributorByEmailSpec : Specification<Contributor>
{
public ContributorByEmailSpec(string email)
{
Query.Where(c => c.Email == new ContributorEmail(email));
}
}
// Utilizzo
var spec = new ContributorByEmailSpec("test@example.com");
var contributor = await _repository.FirstOrDefaultAsync(spec);
Questo approccio permette di:
- Riutilizzare logica di query complessa
- Testare le query in isolamento
- Mantenere il repository generico e minimale
11. Testing e testabilità
La Clean Architecture rende il testing estremamente semplice. Il domain model può essere testato senza database o infrastruttura:
[Fact]
public void UpdateName_ShouldRaiseDomainEvent()
{
// Arrange
var contributor = new Contributor("Old Name", "test@test.com");
var newName = new ContributorName("New Name");
// Act
contributor.UpdateName(newName);
// Assert
contributor.DomainEvents.Should().ContainSingle(
e => e is ContributorNameUpdatedEvent);
}
Per i test di integrazione, ASP.NET Core fornisce WebApplicationFactory:
public class ContributorApiTests : IClassFixture<WebApplicationFactory<Program>>
{
private readonly HttpClient _client;
public ContributorApiTests(WebApplicationFactory<Program> factory)
{
_client = factory.CreateClient();
}
[Fact]
public async Task UpdateContributor_ReturnsSuccess()
{
var request = new { name = "Updated Name" };
var response = await _client.PutAsJsonAsync("/contributors/1", request);
response.StatusCode.Should().Be(HttpStatusCode.OK);
}
}
12. Template e strumenti pratici
Per iniziare rapidamente con Clean Architecture in ASP.NET Core 10, esistono template ufficiali installabili via NuGet:
# Template completo con multi-progetto
dotnet new install Ardalis.CleanArchitecture.Template
dotnet new clean-arch -o MioProgetto
# Template minimale single-project
dotnet new install Ardalis.MinimalClean.Template
dotnet new min-clean -o MioProgettoMinimale
Il template completo include supporto per .NET Aspire, Entity Framework Core, MediatR, FluentValidation e test pronti all’uso. Include anche Papercut per testare l’invio email in ambiente di sviluppo.
Altri strumenti utili:
- NsDepCop: analyzer che previene violazioni della Dependency Rule
- Ardalis.Specification: libreria per implementare il pattern Specification
- Ardalis.GuardClauses: per validazione defensive programming
13. Vertical Slice Architecture vs Clean Architecture
Una critica comune alla Clean Architecture è che richiede di navigare tra molteplici progetti per implementare una singola feature. La Vertical Slice Architecture organizza il codice per feature invece che per layer, riducendo questo problema.
La soluzione ibrida è usare NsDepCop in un progetto single-solution per avere organizzazione verticale ma enforcement delle regole architetturali:
{
"IsEnabled": true,
"AnalyzerServiceCallLimit": 1000,
"DisallowedDependencies": [
{
"From": "Domain",
"To": ["Infrastructure", "Web"]
}
]
}
14. Conclusioni: l’importanza della formazione continua
Implementare correttamente la Clean Architecture in ASP.NET Core 10 richiede una solida comprensione di principi architetturali, pattern di design e best practice. Non si tratta semplicemente di seguire una struttura di cartelle, ma di comprendere profondamente i trade-off e applicare i principi SOLID.
La differenza tra un progetto di successo e uno destinato a diventare legacy prematuro sta nella competenza del team di sviluppo. Solo attraverso la formazione continua e seria è possibile:
- Ridurre gli errori attraverso una migliore architettura e testing
- Migliorare la manutenibilità con codice disaccoppiato e testabile
- Aumentare la produttività con pattern consolidati e riutilizzabili
- Costruire sistemi scalabili che resistono alla prova del tempo
La Clean Architecture non è una bacchetta magica, ma un insieme di principi che, se compresi e applicati correttamente da un team preparato, possono trasformare radicalmente la qualità del software prodotto.
Investire nella formazione del team è l’investimento più importante che un’azienda possa fare per il successo dei propri progetti software.
Formazione Aziendale con Innovaformazione
Innovaformazione offre corsi su misura per aziende informatiche che vogliono portare i propri team di sviluppatori a un livello superiore. I nostri percorsi formativi sono completamente personalizzabili in termini di calendario, contenuti e modalità di erogazione.
Corso ASP.NET Core
Il nostro Corso ASP.NET Core è progettato specificamente per team di sviluppo che vogliono padroneggiare le moderne architetture software, includendo e se serve personalizzando concetti come:
- Clean Architecture e Domain-Driven Design
- Dependency Injection e principi SOLID
- Testing automatizzato (unit, integration, functional)
- API design con FastEndpoints o Minimal APIs
- Entity Framework Core e pattern Repository/Specification
- Best practice per applicazioni enterprise
Modalità di erogazione: Online in classe virtuale con docenti esperti Calendario: Completamente personalizzabile in base alle esigenze aziendali Durata: Modulare, da 2 a 5 giornate
Finanziamenti Fondimpresa
Innovaformazione assiste le aziende nell’accesso ai fondi Fondimpresa per la formazione finanziata. Il nostro team si occupa di:
- Analisi dei fabbisogni formativi
- Progettazione del piano formativo
- Gestione delle pratiche burocratiche
- Rendicontazione e compliance
In questo modo, la formazione del vostro team può essere completamente o parzialmente finanziata, riducendo significativamente i costi diretti per l’azienda.
Contatti
Per richiedere un preventivo personalizzato o maggiori informazioni sui nostri corsi aziendali:
Email: info@innovaformazione.net
Tel: 347 1012275 (Dario Carrassi)
Visita il sito per maggiori dettagli sul corso: Corso Aspe.net Core
Per altri articoli tecnici consigliamo di navigare sul nostro blog QUI.
Articoli correlati
Claude Code e Migrazioni SAP
Claude Code controllo remoto
Opportunità Carriera Contabilità SAP
Guida SIA AI
Guida Dual LLM Verification
