Vai al contenuto

Command Query Separation (CQS e CQRS). Alcuni esempi

Il “Command Query Separation” (CQS) è un principio di progettazione del software che suggerisce di separare i metodi che modificano lo stato dell’oggetto (comandi) da quelli che restituiscono informazioni senza modificarlo (query).

Questo principio è stato proposto da Bertrand Meyer, ed è spesso associato al concetto di “Command-Query Responsibility Segregation” (CQRS), che va oltre il CQS separando completamente i modelli di lettura e scrittura.

Ecco una breve spiegazione di Command Query Separation (CQS):

  1. Comandi (Commands): I comandi sono operazioni che modificano lo stato di un oggetto o del sistema. Questi metodi non restituiscono alcun valore, ma possono avere effetti collaterali. Ad esempio, un metodo AggiungiElemento che aggiunge un elemento a una lista è un comando.
    public class Lista {
        private List<String> elementi = new ArrayList<>();
    
        public void AggiungiElemento(String elemento) {
            this.elementi.add(elemento);
        }
    }
  2. Query: Le query sono operazioni che restituiscono informazioni senza modificare lo stato dell’oggetto o del sistema. Questi metodi non dovrebbero causare effetti collaterali. Ad esempio, un metodo OttieniNumeroElementi che restituisce il numero di elementi in una lista è una query.
    public class Lista {
        private List<String> elementi = new ArrayList<>();
    
        public int OttieniNumeroElementi() {
            return this.elementi.size();
        }
    }

Separando chiaramente i comandi dalle query, è possibile ottenere alcuni benefici, tra cui:

  • Chiarificazione del codice: Rendendo esplicita la differenza tra operazioni che modificano lo stato e quelle che lo leggono, il codice diventa più chiaro e comprensibile.
  • Testabilità: È più semplice testare metodi di query, in quanto non si devono gestire effetti collaterali.
  • Manutenibilità: La separazione può facilitare la manutenzione e l’evoluzione del codice, in quanto i cambiamenti nei requisiti di lettura o scrittura possono essere gestiti in modo più indipendente.

Mentre il CQS è un principio utile, la sua implementazione può variare a seconda del contesto e dei requisiti specifici del progetto.

CQRS

Il principio di “Command Query Responsibility Segregation” (CQRS) suggerisce di separare i modelli di lettura (queries) dai modelli di scrittura (commands) all’interno di un’applicazione. Questo significa che, anziché utilizzare un modello di dominio unico per gestire sia le letture che le scritture, si utilizzano modelli separati ottimizzati per ciascuna di queste operazioni.

Ecco un esempio di implementazione di CQRS in PHP, utilizzando classi separate per i comandi e le query:

// Classe di comando (Command)
class AggiungiElementoCommand {
    private $elemento;

    public function __construct($elemento) {
        $this->elemento = $elemento;
    }

    public function getElemento() {
        return $this->elemento;
    }
}

// Classe di query (Query)
class OttieniNumeroElementiQuery {
}

// Handler per il comando
class AggiungiElementoCommandHandler {
    private $lista;

    public function __construct(Lista $lista) {
        $this->lista = $lista;
    }

    public function handle(AggiungiElementoCommand $command) {
        $elemento = $command->getElemento();
        $this->lista->AggiungiElemento($elemento);
    }
}

// Handler per la query
class OttieniNumeroElementiQueryHandler {
    private $lista;

    public function __construct(Lista $lista) {
        $this->lista = $lista;
    }

    public function handle(OttieniNumeroElementiQuery $query) {
        return $this->lista->OttieniNumeroElementi();
    }
}

// Classe di dominio (Lista)
class Lista {
    private $elementi = [];

    public function AggiungiElemento($elemento) {
        $this->elementi[] = $elemento;
    }

    public function OttieniNumeroElementi() {
        return count($this->elementi);
    }
}

// Utilizzo di CQRS
$lista = new Lista();

// Esegui il comando
$aggiungiElementoCommand = new AggiungiElementoCommand("Elemento 1");
$aggiungiElementoCommandHandler = new AggiungiElementoCommandHandler($lista);
$aggiungiElementoCommandHandler->handle($aggiungiElementoCommand);

// Esegui la query
$ottieniNumeroElementiQuery = new OttieniNumeroElementiQuery();
$ottieniNumeroElementiQueryHandler = new OttieniNumeroElementiQueryHandler($lista);
$numeroElementi = $ottieniNumeroElementiQueryHandler->handle($ottieniNumeroElementiQuery);

echo "Numero di elementi nella lista: " . $numeroElementi;

In questo esempio:

  • La classe AggiungiElementoCommand rappresenta un comando per aggiungere un elemento alla lista.
  • La classe OttieniNumeroElementiQuery rappresenta una query per ottenere il numero di elementi nella lista.
  • I gestori (AggiungiElementoCommandHandler e OttieniNumeroElementiQueryHandler) gestiscono l’esecuzione dei comandi e delle query rispettivamente.
  • La classe Lista è il modello di dominio responsabile della gestione degli elementi.

L’implementazione specifica di CQRS può variare a seconda delle esigenze e della complessità del tuo progetto. L’obiettivo principale è separare le responsabilità di lettura e scrittura per migliorare la manutenibilità e le prestazioni dell’applicazione.

Concetti chiave di CQRS

  1. Separazione dei modelli di lettura e scrittura: Comandi (Commands), rappresentano azioni che modificano lo stato dell’applicazione. Ad esempio, aggiungere un elemento a un carrello. Query, rappresentano richieste per ottenere informazioni, senza apportare modifiche. Ad esempio, ottenere il numero di elementi in un carrello.
  2. Modelli di dominio distinti: CQRS suggerisce l’uso di modelli di dominio distinti ottimizzati per gestire comandi e query separatamente. Questo può semplificare la progettazione e consentire l’ottimizzazione per requisiti specifici.
  3. Architettura separata per le letture e le scritture: L’architettura di un’applicazione CQRS spesso separa fisicamente le componenti che gestiscono comandi da quelle che gestiscono query.

Vantaggi di CQRS

  1. Migliorata flessibilità e scalabilità: La separazione tra comandi e query consente di ottimizzare ciascuna parte indipendentemente. Ad esempio, è possibile scalare le operazioni di lettura separatamente da quelle di scrittura per gestire carichi di lavoro diversi.
  2. Migliore adattabilità del modello di dominio: L’uso di modelli di dominio separati permette di adattare ogni modello alle esigenze specifiche, migliorando la chiarezza e la manutenibilità del codice.
  3. Performance ottimizzata: I modelli di lettura possono essere ottimizzati per operazioni di query specifiche, senza dover gestire la complessità delle logiche di scrittura.
  4. Supporto a materializzazioni di vista: Le materializzazioni di vista (View Materialization) consentono di memorizzare in modo ottimizzato le viste pre-calcolate dei dati per ridurre il tempo di risposta delle query.

Considerazioni importanti

  1. Complessità aggiuntiva: CQRS può aggiungere complessità all’applicazione. È importante valutare attentamente se i benefici derivanti dalla separazione delle responsabilità superano i costi aggiuntivi di sviluppo e manutenzione.
  2. Consistenza eventuali: Dato che i comandi possono impiegare del tempo per essere elaborati, l’applicazione può operare con una consistenza eventuali piuttosto che immediata. Questo è un aspetto importante da considerare durante la progettazione.
  3. Necessità di sincronizzazione: In un sistema CQRS, è necessario gestire la sincronizzazione tra i modelli di lettura e scrittura per garantire che riflettano accuratamente lo stato dell’applicazione.

CQRS è spesso utilizzato in combinazione con il modello di Event Sourcing, in cui gli eventi rappresentano le azioni che modificano lo stato dell’applicazione, consentendo una visione completa della storia delle modifiche. L’adozione di CQRS dovrebbe essere valutata attentamente in base alle specifiche esigenze e complessità dell’applicazione.