CQRS e divisione lettura/scrittura
Cos'è il CQRS
CQRS (Command Query Responsibility Segregation) è un approccio architettonico che condivide il modello di dati e i componenti responsabili della scrittura (commands) e della lettura (queries).
L'idea è che il processo di modifica dello stato sia ottimizzato per gli invarianti validi e le transazioni, mentre la lettura per le proiezioni veloci, mirate e scalabili.
Perché è necessario
Prestazioni di lettura: proiezioni materializzate in scenari specifici (nastri, report, directory).
La stabilità del percorso critico è isolata da joine e aggregazioni pesanti.
Libertà di selezione dello storage: OLTP per la scrittura, OLAP/cache/motori di ricerca per la lettura.
Evoluzione rapida: aggiungere nuove viste senza rischiare di rompere le transazioni.
Osservabilità e verifica (in particolare nel collegamento con Event Source): è più facile ripristinare e sovrascrivere lo stato.
Quando applicare (e quando no)
Adatto se:- Prevalgono le letture con tagli di dati diversi e aggregazioni complesse.
- Il percorso di scrittura critico deve essere sottile e prevedibile.
- Servono SLO/SLA diversi per la lettura e la scrittura.
- È necessario isolare la logica di scrittura del dominio dalle esigenze analitiche/di ricerca.
- Il dominio è semplice, il carico è basso; CRUD se la cava.
- Una forte coerenza tra lettura e scrittura è obbligatoria per tutti gli script.
- Il team è inesperto e la complessità operativa è inaccettabile.
Concetti di base
Comando (Command) - Intende modificare lo stato ('CreateOrder', 'CapturePayment'). Controlla gli invarianti.
Query - Recupero dei dati («GetOrderById», «ListUserTransactions»). Niente effetti collaterali.
Modello di scrittura: aggregazioni/invarianti/transazioni; memorizzazione - relazionale/chiave-valore/evento.
Modello di lettura (proiezioni) - Tabelle/indici/cash materializzate, sincronizzate in modo asincrono.
Coerenza: spesso eventual tra scrittura e lettura; percorsi critici attraverso la lettura diretta da un modello write.
Architettura (scheletro)
1. Servizio Write: accetta comandi, valuta invarianti, registra modifiche (database o eventi).
2. Outbox/CDC - Pubblicazione garantita del fatto di modifica.
3. Processori di proiezione: ascolti eventi/CDC e aggiorna i modelli read.
4. Servizio read: gestisce queries da viste/caselle/ricerche materializzate.
5. Sags/orchestrazione coordinano i processi di aggregazione crociata.
6. Osservabilità: layout di proiezioni, percentuale di applicazioni riuscite, DLQ.
Progettazione di un modello di scrittura
Aggregazioni: limiti di transazione chiari (ad esempio Order, Payment, UserBalance).
Invarianti: formalizza (≥ 0, unicità, limiti).
I comandi sono idipotenti (ad esempio, «idempotency _ key»).
Le transazioni sono minime per portata; effetti collaterali esterni tramite outbox.
Esempio di comando (pseudo-JSON)
json
{
"command": "CapturePayment",
"payment_id": "pay_123",
"amount": 1000,
"currency": "EUR",
"idempotency_key": "k-789",
"trace_id": "t-abc"
}
Progettazione del modello di lettura
Respingere le richieste: quali schermate/report sono necessari?
La denormalizzazione è valida: il modello read è una cache ottimizzata.
Più proiezioni per diverse attività: ricerca (OpenSearch), report (archivio colonne), schede (KV/Redis).
TTL e intersezione: le proiezioni devono essere in grado di ripristinare dall'origine (repliche di eventi/snapshot).
Coerenza e UX
Eventual consistency: l'interfaccia può mostrare i dati più recenti in breve tempo.
Pattern UX: «i dati vengono aggiornati»..., ottimistico UI, indicatori di sincronizzazione, blocco delle azioni pericolose prima della conferma.
Per le operazioni che richiedono una forte coerenza (ad esempio, la visualizzazione di un bilanciamento preciso prima del prelievo), leggere direttamente dal modello write.
CQRS e Event Source (opzionale)
Event Source (ES) memorizza gli eventi e lo stato dell'aggregazione è il risultato della loro coagulazione.
Il collegamento CQRS + ES fornisce un controllo perfetto e una facile intersezione delle proiezioni, ma aumenta la complessità.
Alternativa: normale OLTP-DATABASE + outbox/CDC di proiezione.
Replica Outbox e CDC
Outbox (in una singola transazione) - Scrittura delle modifiche di dominio + scrittura di un evento in outbox; Il Pabliser lo porta nello pneumatico.
CDC - La lettura da un database di database (Debezium, ecc.) → la trasformazione in eventi di dominio.
Garanzia: per impostazione predefinita, i consumatori e le proiezioni devono essere idipotenti.
Selezione dello storage
Write: relazionale (PostgreSQL/MySQL) per le transazioni; KV/Comment - dove gli invarianti sono semplici.
Read:- KV/Redis - schede e letture chiave veloci;
- Ricerca (OpenSearch/Elasticsearch) - Ricerca/filtri/sfaccettature
- Colonne (ClickHouse/BigQuery) - Report;
- Kesh su CDN - cataloghi/contenuti pubblici.
Modelli di integrazione
livello API: endpoint/servizi separati per commands e queries.
Idampotenza: chiave dell'operazione nel titolo/corpo; storage recent-keys con TTL.
Sags/orchestrazione: timeout, compensi, ripetitività dei passi.
Backpressure limita il parallelismo dei processori di proiezione.
Osservabilità
Metriche write: p95/99 latitanza dei comandi, percentuale di transazioni riuscite, errori di convalida.
Metriche read: p95/99 query, hit-rate cache, carico del cluster di ricerca.
Combinazione di proiezioni (tempo e messaggi), puntata DLQ, percentuale di deduplicazione.
Tracing: «trace _ id» passa attraverso il comando outbox per la proiezione query.
Sicurezza e compilazione
Separazione dei diritti: scopes/ruoli diversi per la scrittura e la lettura il principio dei minori privilegi.
PII/PCI: minimizza le proiezioni; crittografia at-rest/in-flight occultamento.
Controllo: fissa il comando, l'attore, l'esito, 'trace _ id'; Archivi WORM per domini critici (pagamenti, KYC).
Test
Contract test: per i comandi (errori, invarianti) e queries (formati/filtri).
Progetti test - Invia una serie di eventi/CDC e controlla il modello read finale.
Chaos/latency: inserimento di ritardi nei processori di proiezione Verifica UX durante il raggio.
Replayability - Intersezione delle proiezioni sullo stand da snapshot/login.
Migrazione e evoluzione
I nuovi campi sono additivi nell'evento/CDC; i modelli read vengono riarrangiati.
Doppia voce (dual-write) per i diagrammi ridisegnati teniamo le vecchie proiezioni fino al cambio.
Versioning: «v1 »/« v2» eventi e endpoint, piano sunset.
Feature flags - Immettere nuove queries/proiezioni canarie.
Antipattern
CQRS «per la moda» in semplici servizi CRUD.
Severa dipendenza sincrona dalla lettura dalla scrittura (uccide l'isolamento e la resistenza).
Un indice per tutto: mescolare le richieste eterogenee in un read-store.
Nessuna idampotenza nelle proiezioni di doppie e discrepanze.
Proiezioni non riparabili (nessun replay/snapshot).
Esempi di domini
Pagamenti (servizio online)
Write: «Authorize», «Capture», «Refund» nel database transazionale; outbox pubblica'payment '.
Read:- Redis «carta di pagamento» per la UI;
- ClickHouse per i report
- OpenSearch per la ricerca delle transazioni.
- Percorso critico: autorizzazione ≤ 800 ms p95 coerenza di lettura UI - avvenual (fino a 2-3 c).
KYC
Write: comandi di avvio/update dello stato; Deposito di PII in un database protetto.
Read: proiezione semplificata degli stati senza PII; Il PII viene tirato a punto se necessario.
Protezione: scopes diversi per la lettura dello stato e l'accesso ai documenti.
Bilanci (iGaming/Finanza)
Write: aggregato' UserBalance 'con aggregati/decrementi atomici; chiavi idonee per l'intervento.
Read: cache per «bilanciamento rapido» per il prelievo - lettura diretta da write (rigorosa coerenza).
I depositi e le conclusioni sono coordinati da eventi, in caso di guasti di risarcimento.
Assegno foglio di implementazione
- Le unità e gli invarianti del modello write sono selezionati.
- Definite le queries chiave e progettate le proiezioni sottostanti.
- Sono stati configurati outbox/CDC e processori di proiezione idipotenti.
- C'è un piano per l'intersezione delle proiezioni (snapshot/replay).
- SLO: latitanza dei comandi, pianificazione delle proiezioni, disponibilità di read/write singolarmente.
- I diritti di accesso e la crittografia dei dati sono stati condivisi.
- Gli alert su DLQ/lega/errori di deduplicazione.
- Test: contratti, proiezioni, caos, repliche.
FAQ
L'Event Source per CQRS è necessario?
No, no. Puoi costruire su un database normale + outbox/CDC.
Come si combatte la rassincronizzazione?
Progettare chiaramente UX, misurare la banda di proiezioni, permettere alle operazioni critiche di leggere da write.
È possibile mantenere sia write che read nello stesso servizio?
Sì, la separazione fisica è opzionale; la condivisione logica delle responsabilità è obbligatoria.
E le transazioni tra aggregazioni?
Attraverso le saghe e gli eventi; evitare transazioni distribuite, se possibile.
Totale
CQRS disattiva le mani con un percorso di scrittura sottile e affidabile con invarianti chiari e letture veloci e mirate da proiezioni materializzate. Migliora la produttività, semplifica l'evoluzione e rende il sistema più resistente ai carichi di lavoro - se gestito in modo disciplinato da coerenza, osservabilità e migrazioni.