CQRS este un şablon arhitectural care recomandă separarea responsabilităţilor între procesarea comenzilor (engl. command processing responsibility) şi interogare (engl. query responsibility). Prin urmare, şablonul spune că nu este necesară aceeaşi sursă de stocare de date şi nici măcar aceeaşi tehnologie pentru mecanismele de scriere şi citire.
Tratarea separată a celor două tipuri de reponsabilităţi ar trebui implementată în două nivele (engl. layere) diferite ale aplicaţiei.
Şablonul recunoaşte că interogarea şi procesarea comenzilor sunt fundamental diferite.
Având în vedere cele spuse mai sus, are sens să folosim două modele diferite, unul pentru asigurarea regulilor de business şi unul pentru prezentarea informaţiei.
Un aspect important de menţionat este faptul că nivelului de procesare a comenzilor NU îi este interzis să citească date. Citirea datelor este aproape întotdeauna necesară pentru implementarea nivelului de comandă! Este important totuşi de subliniat că cerinţele de citire ale nivelului de procesare a comenzilor sunt fundamental diferite de cele ale nivelului de interogare. De exemplu, citirile făcute de nivelul de comandă sunt în general legate de cheia primară (engl. primary key) a entităţilor şi logica de citire este implementată direct în nivelul de procesare a comenzilor. În schimb, nivelul de interogare trebuie să furnizeze un mecanism de filtrare pentru a permite utilizatorilor să găsească datele de care au nevoie.
Ca indicii pentru implementare, la folosirea bazelor de date SQL pentru stocare, indecşii bazei de date de procesare a comenzilor sunt probabil complet diferiţi de indecşii bazei de date de interogare. Pe de o parte, baza de date de procesare a comenzilor nu are nevoie de date istorice, care pot fi şterse în siguranţă, pentru a creşte performanţa, iar pe de altă parte, baza de date pentru interogare va păstra probabil datele istorice, pentru un interval rezonabil de timp.
Mergând mai departe pe această idee, există probabilitatea ca baza de date pentru interogare să nu menţină toate datele istorice, ci doar pe cele mai des folosite. Ce înseamnă date istorice folosite des şi pentru cât timp sunt stocate acestea, sunt întrebări al căror răspuns depinde integral de aplicaţie. Referitor la datele istorice cu adevărat vechi, care pot apărea din când în când în anumite rapoarte, acestea trebuie să fie stocate într-o bază de date dedicată raportării, accesată mai rar.
Ceea ce interzice cu adevărat CQRS, este ca nivelul de interogare să facă modificări în date (acces de scriere)! Reţineţi baza de date denormalizată şi asumpţia că nivelul de interogare nu implică considerente de integritate şi nu necesită consistenţă tranzacţională.
Alt argument în favoarea şablonului CQRS este observaţia că frecvenţa de citire este diferită de frecvenţa de scriere. Utilizatorii au de regulă mai multe solicitări de citire pentru fiecare comandă de scriere (în caz că există). Prin urmare este simplu de înţeles că partea de citire a aplicaţiei are nevoie de scalare mai mult decât cea de scriere.
Mai mult decât atât, nu este nevoie să prezentăm datele din aceeaşi bază de date folosită pentru scriere, deoarece acestea nu trebuie neapărat să fie actualizate instantaneu şi de regulă nici nu sunt. Chiar dacă datele curente sunt citite din baza de date, odată prezentate pe ecran, ele devin deja vechi şi potenţial irelevante.
Aplicaţiile cu încărcare (engl. load) ridicată folosesc oricum caching,dar doar ca o optimizare ulterioară nu ca o decizie arhitecturală. Aşa că nu ne rămâne decât să acceptăm cache-ul şi faptul că datele prezentate pot fi vechi, şi să încorporăm aceste realităţi în arhitectura noastră. Astfel aplicaţia s-ar simplifica mult şi ar creşte performanţa, după cum va fi descris în cele ce urmează.
Pe lângă acela de a putea controla scalarea între accesul de scriere şi de citire, CQRS are şi alte beneficii, după cum puteţi vedea mai jos:
Probabil că nu veţi folosi CQRS la implementarea unor aplicaţii foarte simple sau în care modelul de scriere e similar celui de citire sau foarte uşor adaptabil. Mai mult decât atât, şi cel mai important, este să nu vă aşteptaţi la schimbări pe viitor, care să crească complexitatea nivelului de business aşa încât menţinerea ambelor modele (citire şi scriere) în aceleaşi clase să devină greoaie - deşi, în practică, cum aţi putea prezice aşa ceva într-un mediu agil?
În orice caz, cu excepţia aplicaţiilor cu complexitate redusă la nivel de business, care evoluează lent (sau nu evoluează deloc), ar fi recomandat să se planifice şi să se separe cele două modele şi cele două responsabilităţi, chiar dacă nu se vor folosi două baze de date.Din experiența noastră vă putem oferi câteva detalii. Am folosit această strategie şi am implementat două modele: un domain model deplin - pentru partea de procesare a comenzilor, şi un model de citire (de fapt un view-model în limbajul MVC) pentru partea de interogare, folosind aceeaşi bază de date. Am procedat aşa pentru că am simţit că CQRS se referă mai mult la separarea responsabilităţilor între procesarea comenzilor şi interogare decât la separarea mediilor fizice de stocare de date. Nivelul de interogare a folosit vederi (engl. views) în timp ce nivelul de procesare a comenzilor a folosit tabele. Avem toate beneficiile CQRS, exceptând scalarea diferenţiată între citire şi scriere, dar totodată şi fără efortul de a sincroniza baza de date de interogare cu cea de procesare a comenzilor. Dacă ar trebui scalare, s-ar putea crea uşor o bază de date de interogare cu tabelele identice cu vederile implementate şi doar s-ar adăuga un mecanism ETL (Extract, Transform and Load) pentru sincronizarea cu baza de date de procesare a comenzilor. Totuşi trebuie evidenţiat faptul că schimbarea de la vederile din aceeaşi bază de date, la tabele în baze de date diferite, menţinute în sincronizare prin ETL asincron, ar însemna că aplicaţia nu mai poate presupune că datele modificate sunt disponibile sincron cu executarea comenzilor.
Succesul practicilor Agile în dezvoltarea unui produs nu e doar o problemă de a implementa corect metodologia aleasă (SCRUM, Kanban, etc.) ci este necesar ca şi arhitectura să permită produsului să evolueze .
Arhitectura rigidă, cuplajul mare, coeziunea scăzută sau complexitatea ridicată contribuie la ceea ce putem numi inerţia aplicaţiei. Ca şi în fizică, inerţia reprezintă rezistenţa aplicaţiei la schimbare. Evident că astfel s-ar învinge orice încercare de utilizare a proceselor agile în dezvoltarea software, ducând doar la o aplicare superficială a acestora. Prin urmare, CQRS ajută enorm şi chiar şi acest fapt în sine îi justifică utilitatea.
CQRS e benefic şi prin faptul că ne permite să avem două modele deconectate în aceeaşi aplicaţie:
Este important de menţionat că aceste două modele, fiecare gestionat de nivelul corespunzător (cel de procesare a comenzilor şi respectiv cel de interogare), pot evolua independent, fără a fi conectate. Prin urmare nu există cuplaj între ele, fiecare fiind interesat doar de propriul său scop, având astfel coeziune ridicată - esenţial pentru ca aplicaţia să poată evolua în mod agil.
Prin urmare, dacă ar apărea cerinţe noi referitor la regulile de business, cel mai probabil doar domain model-ul de pe partea de procesare a comenzilor va fi afectat. Similar, dacă apar cerinţe noi pe partea de prezentare,poate apărea probabilitatea ca doar modelul de citire de pe partea de interogare să fie afectat. Dar, chiar şi dacă apar cerinţe care ating ambele modele din ambele nivele, fiecare nivel evoluează independent pentru a le face faţă. Această abordare este totuşi mai bună decât schimbarea unui model foarte complex care conţine cele două responsabilităţi.
Mai mult decât atât, agilitatea nu se referă doar la gestionarea cerinţelor funcţionale pe un produs existent, ci şi la felul în care dezvoltăm un produs nou. Ei bine, CQRS ne ajută şi aici, deoarece ne permite să simulăm partea de procesare a comenzilor, permiţând totuşi crearea unui model pentru citire cu un model de interogare simulat. Aplicaţia permite extinderea framework-ului pe măsură ce noi cerinţe funcţionale apar.
În cele din urmă, abilitatea de a face uşor simulări (engl. mocks) pentru anumite componente din aplicaţie este o condiţie importantă pentru a avea acoperire adecvată cu teste unitare (engl. unit tests). Astfel se permite implementarea TDD (Test-Driven Development) sau a BDD (Behavior-Driven Development) care asigură o fundaţie bună (dacă nu esenţială) pentru orice metodologie agilă de dezvoltare.
Mă bucur că pot spune pe scurt - securitatea nu este responsabilitatea directă a niciunuia dintre nivele - de interogare sau de procesare a comenzilor.
Securitatea, cu referire la informaţia afişată, poate fi de regulă implementată ca o serie de filtre impuse.Decizia de a impune filtre pe date nu e facută de nivelul de interogare ci mai degrabă de un nivel superior.
Cu privire la securitatea în ceea ce priveşte manipularea datelor de către un actor, aceasta pare mai degraba o problemă de scenarii de utilizare (engl. use cases), deoarece fiecare actor va fi încadrat în unul din rolurile predefinite ale aplicaţiei. Prin urmare, nivelul de comandă nu e responsabil cu asigurarea securităţii ci doar cu execuţia comenzilor. A valida dacă un rol dat ar trebui să aibă dreptul să execute o comandă - dependent de rol, de comandă şi de parametrii săi - este sarcina unui nivel superior nivelului de procesare a comenzilor.
Aşadar, decizia de a utiliza CQRS nu are un impact foarte mare asupra considerentelor de securitate.
Ca la orice nou şablon, concept sau tehnologie, şi la CQRS există anumite riscuri în aplicare, cu atât mai mult cu cât el presupune o schimbare de paradigmă. În acest caz, faptul că baza de date pentru citire poate fi diferită de baza de date pentru scriere, şi existența a două modele, unul pentru interogare şi unul pentru procesarea comenzilor, reprezintă o schimbări de paradigmă ce implică timp de acomodare până va putea fi aplicată corespunzător.
Totodată, complexitatea aplicației se accentuează pentru că datele modificate nu sunt imediat disponibile pentru prezentare odată ce comanda s-a executat.
CQRS este un şablon puternic datorită motivelor mai sus menţionate, pe care le vom reitera în cele ce urmează:
Beneficiul maxim adus de CQRS apare atunci când aplicaţia este complexă şi ar avea nevoie de scalare dar, oricum, CQRS are avantaje chiar şi în cazurile care nu implică scalabilitate.
Recomandăm ca detaliile specifice aplicaţiei să fie analizate înainte de adoptarea CQRS, dar considerăm că în general utilizarea acestui şablon ar fi o idee bună!