Definizione dei casi d'uso dell'applicazione
In questa sezione esploreremo come definire i casi dâuso della tua applicazione utilizzando Lino CLI. I casi dâuso rappresentano interazioni specifiche tra utenti o sistemi esterni e lâapplicazione, incapsulando le regole di business e promuovendo unâarchitettura orientata al dominio. Tratteremo la separazione tra operazioni di lettura e scrittura tramite il pattern CQRS, oltre a standardizzare le risposte utilizzando il Result Pattern.
Il pattern CQRS (Command Query Responsibility Segregation) propone la separazione tra le operazioni che modificano lo stato dellâapplicazione (Commands) e quelle che si limitano a leggere i dati (Queries). Questo approccio consente di ottimizzare ciascun tipo di operazione in modo indipendente, migliorando la scalabilitĂ , le prestazioni e la manutenibilitĂ del sistema.
Il Result Pattern viene utilizzato per standardizzare i risultati delle operazioni, incapsulando informazioni su successo o fallimento, messaggi di errore e dati risultanti. Questo facilita una gestione coerente delle risposte nei diversi livelli dellâapplicazione.
Vedremo come sono strutturati i file di Commands e Queries, le validazioni dei dati, come viene applicata la logica di dominio nei Handlers e come restituire i risultati in modo standardizzato. Tutto questo grazie alla creazione automatica di questi componenti tramite il CLI di Lino.
Nota: Sebbene non sia obbligatorio, attualmente Lino offre due opzioni per applicare il pattern CQRS nel livello applicativo: utilizzando il pattern Mediator tramite la libreria MediatR (di Jimmy Bogard) oppure la libreria Mediator (di Martin Othamar).
Panoramica dei Use Case
Un Use Case rappresenta unâinterazione completa tra utenti o sistemi esterni e lâapplicazione, descrivendo scenari di business specifici. In Lino, ogni Use Case è suddiviso in:
- Command: rappresenta lâintenzione di modificare lo stato del sistema (creare, aggiornare, eliminare, ecc.).
- Query: rappresenta lâintenzione di interrogare i dati senza modificare lo stato del dominio.
Questa separazione favorisce la chiarezza del codice, facilita i test, consente scalabilitĂ indipendente per lettura e scrittura, e si allinea ai principi di architettura pulita e alle buone pratiche del Domain-Driven Design (DDD).
Comandi
Un Command è un messaggio immutabile che contiene solo i dati necessari per eseguire un'azione che modifica lo stato del sistema (ad esempio, CreateInvoice, DeactivateUser).
Deve contenere solo le proprietĂ che rappresentano le informazioni essenziali per eseguire l'operazione.
Caratteristiche di un Command
- ImmutabilitĂ : implementato come un
recordo una classe con sologete senza setter pubblici. - Nome allâimperativo: riflette lâazione che sarĂ eseguita, per esempio
CreateOrder,UpdateCustomerAddress. - Dati minimi: contiene solo i campi necessari per eseguire lâoperazione, senza restituire grandi quantitĂ di dati.
- Validazione isolata: ogni Command possiede regole di validazione proprie, garantendo che sia coerente prima di arrivare al gestore (Handler).
Validator dei Command
I Command Validators assicurano che il Command sia ben formato e rispetti i requisiti di business prima di essere inviato allâHandler. In Lino utilizziamo la libreria FluentValidation per implementare queste validazioni, perchĂŠ è ampiamente adottata nei progetti .NET e offre una Fluent API per la creazione delle regole.
Regole comuni di validazione
NotEmpty,NotNullper campi obbligatori.InclusiveBetweenper intervalli numerici (es.: valori monetari, quantitĂ minima/massima).MaximumLengtheMinimumLengthper la lunghezza delle stringhe.RuleForEachper la validazione degli elementi nelle collezioni (List<T>).Mustper regole personalizzate (es.: formato documenti, validazione date).
Handler dei Command
Il Command Handler è responsabile di eseguire la logica di dominio associata a quel Command. Orchestri repository, unità di lavoro (IUnitOfWork), servizi ausiliari e genera eventi di dominio quando necessario.
Pattern di implementazione di un Handler
- Ricevere le dipendenze (repository, servizi esterni, UnitOfWork) tramite dependency injection.
- Mappe e istanzi le entitĂ di dominio, garantendo la coerenza iniziale.
- Applichi le regole di business (validazioni aggiuntive, calcolo dei valori, generazione di eventi di dominio).
- Persisti le modifiche tramite
IUnitOfWorko repository diretti. - Ritorni un Command Result che indica successo o fallimento (
Result<T>).
Risultati dei Command e Result Pattern
Il Command Result deve essere un DTO semplice contenente solo i dati minimi necessari affinchĂŠ il chiamante (es., API o front-end) possa proseguire. Generalmente include lâidentificatore dellâentitĂ creata o aggiornata (Id) e, in caso di fallimento, informazioni standardizzate sullâerrore.
Per scenari in cui il Command può fallire, utilizziamo il Result Pattern: unâastrazione che incapsula il risultato di unâoperazione, che può essere successo o fallimento. In .NET è comune usare tipi come Result<T> (o librerie simili), che:
- Permettono di definire un
Valuein caso di successo (es., un DTO semplice). - In caso di fallimento, memorizzano codici o messaggi standardizzati (
Error), possibilmente con metadati extra (codici HTTP, dettagli di validazione, ecc.). - Facilitano lâunificazione del flusso di gestione degli errori in tutta lâAPI, mantenendo coerenza tra i livelli.
Creare un Command con il CLI
Lino semplifica la generazione di tutti gli artefatti necessari per un nuovo Command tramite il comando:
lino command new
Al lancio di questo comando, lâassistente interattivo chiederĂ :
- Servizio â Nome del servizio in cui verrĂ creato il Command.
- Modulo â Nei servizi modulari, definisce il modulo di destinazione.
- EntitĂ â EntitĂ di dominio correlata al Command (opzionale ma consigliata).
- Nome del Command â Nome finale del Command (es.,
CreateOrder).
Dopo la conferma, Lino creerĂ automaticamente i file:
CreateOrderCommand.csCreateOrderCommandValidator.csCreateOrderCommandHandler.csCreateOrderCommandResult.cs
Esempio di struttura generata
Considera il Command CreatePerson. La struttura generata sarĂ simile a:
MyApp/
âââ src/
âââ Services/
âââ MyService/
âââ Application/
âââ MyApp.MyService.Application.csproj
âââ UseCases/
âââ People/
âââ Commands/
â âââ CreatePerson/
â âââ CreatePersonCommand.cs
â âââ CreatePersonCommandValidator.cs
â âââ CreatePersonCommandHandler.cs
â âââ CreatePersonCommandResult.cs
âââ Queries/
âââ ...
Query
Una Query rappresenta lâintento di ottenere dati senza modificare lo stato del dominio. Le Query sono progettate per essere efficienti in lettura, restituendo esattamente i campi necessari, senza caricare intere entitĂ se non richiesto.
Caratteristiche di una Query
- Immutabile â come i Comandi, una Query non deve essere modificabile dopo la sua creazione.
- Nome descrittivo â riflette le informazioni ricercate, ad esempio:
GetCustomerById,ListOrdersByDateRange. - Filtri e paginazione â può accettare parametri per ricerche specifiche (date, stato, pagina, ordinamento).
- Proiezione â deve restituire un DTO o view model con solo i campi necessari, evitando di caricare oggetti dominio completi.
Validatori di Query
I Validatori di Query sono responsabili della validazione dei parametri di input, come i filtri, i valori di paginazione (page, pageSize) e le regole di sicurezza (permessi utente, visibilitĂ dei dati). Sono anchâessi implementati con FluentValidation.
Regole comuni di validazione nelle Query
GreaterThanOrEqualTooLessThanOrEqualToâ per filtri di intervallo (es.: date).Lengthâ per filtri di testo (es.: ricerca per nome o email).InclusiveBetweenâ per parametri di paginazione (es.: numero minimo/massimo di elementi per pagina).
Handler di Query
LâHandler di Query è responsabile dellâinterrogazione dei repository o del contesto del database e del ritorno di proiezioni ottimizzate, evitando di caricare completamente le entitĂ di dominio.
In Lino, si raccomanda lâuso di proiezioni con metodi come .Select() per mappare direttamente i dati nel formato previsto dal DTO, garantendo efficienza e migliori prestazioni.
Risultati delle Query
In Lino, i risultati delle query sono sempre rappresentati da record con il suffisso QueryResult.
Questa convenzione si applica indipendentemente dal tipo di risposta â che si tratti di una lista di elementi (con o senza paginazione) o di un singolo elemento dettagliato.
Creare una Query con il CLI
Come per i Comandi, Lino mette a disposizione il comando:
lino query new
La procedura guidata richiederĂ :
- Servizio â il servizio in cui sarĂ creata la Query.
- Modulo â se applicabile, definisce il modulo di dominio.
- EntitĂ â l'entitĂ o aggregato a cui la Query è associata (opzionale).
- Nome della Query â ad esempio:
GetOrderByIdoListProductsByCategory.
Lino genererĂ automaticamente:
GetOrderByIdQuery.csGetOrderByIdQueryValidator.csGetOrderByIdQueryHandler.csGetOrderByIdQueryResult.cs
Esempio di struttura generata per le Query
MyApp/
âââ src/
âââ Services/
âââ MyService/
âââ Application/
âââ MyApp.MyService.Application.csproj
âââ UseCases/
âââ Orders/
âââ Commands/
| âââ ...
âââ Queries/
âââ GetOrderById/
âââ GetOrderByIdQuery.cs
âââ GetOrderByIdQueryValidator.cs
âââ GetOrderByIdQueryHandler.cs
âââ GetOrderByIdQueryResult.cs
Conclusione
Dopo aver compreso come definire e strutturare i Use Cases (Commands e Queries) in Lino, sei pronto a creare scenari di business robusti e scalabili, segmentando chiaramente la logica di scrittura e lettura. Usa il CLI per accelerare lo sviluppo, ma rivedi e adatta sempre le implementazioni in base alle esigenze specifiche del tuo dominio.
Successivamente, esplora come gestire la persistenza, gli eventi di dominio e i servizi di infrastruttura per comporre lâintera architettura della tua applicazione.
