Vai al contenuto

CQRS, pattern architetturale per la modellazione del dominio di un software

CQRS è l’acronimo di Command and Query Responsibility Segregation, un modello architetturale che separa le operazioni di lettura e aggiornamento per un archivio dati. Possiamo pensare alle Command come blocchi di codice che modificano i dati (creare, aggiornare, eliminare) e alle Query come blocchi di codice che leggono i dati. CQRS sostanzialmente divide le azioni di un sistema/applicazione in due categorie: comando e query, quindi il comando o la query sono progettatati per svolgere una singola responsabilità. L’idea di base della struttura CQRS è che un metodo può restituire un valore o modificare un oggetto. Non accetta di fare entrambe le cose insieme e le valuta diversamente.

L’implementazione del modello CQRS in un’applicazione può massimizzare prestazioni, scalabilità e sicurezza. Per alcune situazioni, questa separazione può essere preziosa, ma attenzione che per la maggior parte dei sistemi CQRS aggiunge complessità rischiosa. La flessibilità creata dalla migrazione al modello CQRS consente a un sistema di evolversi meglio nel tempo e impedisce ai comandi di aggiornamento di causare conflitti di merge a livello di dominio.

Contesto e problema

L’approccio tradizionale utilizzato dagli sviluppatori per interagire con un sistema informativo è trattarlo come un archivio su cui eseguire operazioni CRUD (Create, Read, Update, Delete) largamente utilizzato. Tale modello prevede lo stesso modello dati sia per l’esecuzione delle query che per gli aggiornamenti, rendendolo semplice da implementare. Nel caso più semplice, le nostre interazioni riguardano l’archiviazione e il recupero di questi record.

Man mano che le nostre esigenze diventano più sofisticate, ci allontaniamo costantemente da quel modello. In applicazioni più complesse questo approccio può risultare poco pratico. Ad esempio:

  • nelle operazioni di lettura che eseguono diverse query, restituendo oggetti DTO (Data Transfer Object) in diverse forme e magari più complesse rispetto a quelle base: in questi casi, il mapping degli oggetti può diventare difficoltoso.  Capita molto spesso, infatti, che nel recupero dei dati sia necessario aggregare le informazioni in entità più complesse rispetto a quelle base, e conseguentemente è necessario utilizzare modelli dati diversi rispetto a quelli base con cui i dati sono memorizzati.
  • nelle operazioni di scrittura che implementano convalide e logica di business complesse. Di conseguenza, può risultare un modello eccessivamente complesso che esegue troppe attività.

I carichi di lavoro di lettura e scrittura sono spesso asimmetrici, con requisiti di prestazioni e scalabilità molto diversi.

Il principio fondamentale di CQRS è la separazione di comandi e query come previsto dal principio CQS (Command Query Separation). Svolgendo ruoli fondamentalmente diversi all’interno di un sistema, separarli significa che ognuno può essere ottimizzato secondo necessità, il che può essere estremamente vantaggioso per i sistemi distribuiti.  Poiché i requisiti operativi per l’esecuzione di comandi e query sono spesso molto diversi, gli sviluppatori dovrebbero prendere in considerazione l’utilizzo di diverse tecniche di persistenza per la gestione di comandi e query, separandoli quindi.

Comandi

Un comando è un’istruzione, una direttiva per eseguire un compito specifico. Un comando dovrebbe trasmettere l’intenzione dell’utente. La gestione di un comando dovrebbe comportare una transazione su un aggregato; fondamentalmente, ogni comando dovrebbe indicare chiaramente una modifica ben definita.

I comandi costituiscono il modello di scrittura che dovrebbe essere il più vicino possibile ai processi aziendali.

Query

Una query è una richiesta di informazioni che restituisce un risultato ma non modifica lo stato. È l’intenzione di ottenere dati, o lo stato dei dati, da un luogo specifico. Nulla nei dati deve essere modificato dalla richiesta. Poiché le query non modificano nulla, non è necessario coinvolgere il modello di dominio.

Le query costituiscono  il modello di lettura che dovrebbe essere derivato dal modello di scrittura. Inoltre, non deve essere permanente: nuovi modelli di lettura possono essere introdotti nel sistema senza influire su quelli esistenti. I modelli di lettura possono essere eliminati e ricreati senza perdere la logica di business o le informazioni, poiché sono archiviate nel modello di scrittura.

Un esempio CQRS

Un esempio di CQRS può essere visualizzato durante il processo dell’ordine di un cliente. Quando il cliente esamina un ordine, il processo è relativamente semplice: una lettura da un database. In pratica, quella lettura potrebbe provenire da un archivio di valori-chiave NoSQL, come Redis. Questo valore-chiave archivia tutte le informazioni di facile accesso sul client in un unico posto. Un microservizio raccoglie le informazioni e una pagina Web le visualizza. La parte di lettura è semplice e può essere eseguita interamente dal team Redis che lavora con gli sviluppatori front-end. Pertanto, la responsabilità della lettura di CQRS è separata dalla scrittura.

CQRS e Event Sourcing

La propagazione degli eventi tra il lato del command e il lato della query è un componente fondamentale del modello CQRS. I sistemi basati su CQRS utilizzano modelli di dati di lettura e scrittura separati, in cui ogni modello è adattato alle attività pertinenti e spesso utilizzano archivi dati fisicamente separati.

Le operazioni di scrittura generano un nuovo evento che viene propagato al lato della query al fine di aggiornare le viste dei dati. A tal proposito, si utilizza la tecnica dell’Event Sourcing, che prevede la memorizzazione di ogni evento in uno store denominato Event Store.

Il sistema tiene traccia dei cambiamenti passati negli stati dei dati e avvisa gli utenti su come gestirli. Nell’event sourcing, viene registrata ogni fase del processo di trasformazione dei dati, non solo lo stato più recente. Qualsiasi azione generata dall’utente, come fare clic su un pulsante del mouse o premere un tasto della tastiera, viene gestita come un evento.

L’origine degli eventi suggerisce che il sistema stesso può essere ricreato come raccolta di tutte le modifiche nel tempo. Ciò crea una nuova visualizzazione dei dati all’interno di un sistema software: il registro delle transazioni. Con l’origine degli eventi, il registro è più di un file di testo: è un vero insieme di comandi che possono essere riprodotti per ricreare lo stato dell’applicazione.

In alcuni casi, il registro eventi può essere “riprodotto all’indietro” per annullare modifiche di grandi dimensioni. L’impostazione dell’origine degli eventi richiede l’acquisizione delle modifiche, oltre alla creazione del software per riprodurre tali modifiche.