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 record o una classe con solo get e 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, NotNull per campi obbligatori.
  • InclusiveBetween per intervalli numerici (es.: valori monetari, quantitĂ  minima/massima).
  • MaximumLength e MinimumLength per la lunghezza delle stringhe.
  • RuleForEach per la validazione degli elementi nelle collezioni (List<T>).
  • Must per 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 IUnitOfWork o 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 Value in 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.cs
  • CreateOrderCommandValidator.cs
  • CreateOrderCommandHandler.cs
  • CreateOrderCommandResult.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

  • GreaterThanOrEqualTo o LessThanOrEqualTo — 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: GetOrderById o ListProductsByCategory.

Lino genererĂ  automaticamente:

  • GetOrderByIdQuery.cs
  • GetOrderByIdQueryValidator.cs
  • GetOrderByIdQueryHandler.cs
  • GetOrderByIdQueryResult.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.

Si è verificato un errore non gestito. Ricarica đź—™