ABONAMENTE VIDEO REDACȚIA
RO
EN
×
▼ LISTĂ EDIȚII ▼
Numărul 19
Abonament PDF

Command Query Responsibility Segregation

Andrei Păcurariu
Software arhitect
@Endava
PROGRAMARE

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.

Fig. 1 - O posibilă abordare a CQRS

Argumentare

Şablonul recunoaşte că interogarea şi procesarea comenzilor sunt fundamental diferite.

  • Procesarea comenzilor respectă reguli de business (engl. business rules) uneori extrem de complexe care dictează setul de combinaţii valide pentru manipularea datelor. Pentru procesarea comenzilor, considerentele de consistenţă şi integritate sunt esenţiale.
  • Presupunând folosirea unei baze de date SQL pentru stocare, normalizarea este foarte importantă pentru procesarea comenzilor.
  • Procesarea comenzilor depinde de consistenţa tranzacţională a datelor.
  • Procesarea comenzilor poate fi asincronă , ceea ce reprezintă câteodată un beneficiu important de obţinut.
  • Interogările nu au cerinţe de integritate, deci nu este nevoie nici de normalizare. Bazele de date denormalizate sunt o alegere excelentă, pentru că au interogările mai rapide.
  • Interogările necesită filtre complexe şi agregări de date în beneficiul interfeţei grafice (engl. GUI - Graphical User Interface).

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ă.

De ce să folosim CQRS?

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:

  • Partea de procesare a comenzilor este simplă şi specializată în executarea regulilor de business, fără a fi influenţată de GUI.
  • Partea de interogare este simplă şi specializată în filtrarea şi agregarea datelor, în beneficiul GUI-ului, fără restricţii referitoare la modelul folosit pentru scriere.
  • Permite ca dezvoltarea GUI-ului şi a părţii de interogare să fie făcute în paralel cu dezvoltarea părţii de procesare a comenzilor.
  • Permite nivelelor de interogare şi procesare a comenzilor să evolueze diferit în timp.
  • Permite fiecărui nivel să utilizeze tehnologii specializate. De exemplu, nivelul de procesare a comenzilor ar putea folosi un ORM (Objct-Relational Mapper), precum Entity Framework, pe când nivelul de interogare nu necesită un ORM complet - ar putea fi suficientă utilizarea unui micro-ORM precum Dapper sau chiar neutilizarea unui ORM. Aceasta este din cauză că nivelul de interogare nu necesită un identity map (nume de şablon de proiectare) sau gestiunea modificărilor (engl. change tracking), iar aceste facilităţi nu fac decât să reducă viteza interogărilor! Deoarece impune segregarea responsabilităţilor de interogare şi procesare a comenzilor, arhitectura devine mai simplu de migrat către multi-tier, prin înlocuirea, de exemplu, a nivelului de procesare a comenzilor cu un serviciu.
  • Datorită acestei separări, este posibil ca programatorii seniori să lucreze la domain model-ul pentru procesarea comenzilor, în timp ce juniorii lucrează la achiziţia de date pentru GUI (nivelul de interogare).
  • Folosirea CQRS va face aplicaţia mai compatibilă cu tehnologia Azure, deoarece se pot folosi Table Storage pentru procesarea comenzilor şi SQL Database pentru nivelul de interogare. Table Storage nu e foarte potrivit pentru scenarii de raportare ca de exemplu GUI-ul, dar e foarte util (şi are costuri reduse) pentru procesarea comenzilor. În general, presupun că ar fi suficientă utilizarea cheilor de partiţie şi de rând (engl. partition key and row key) pentru accesarea datelor în interesul procesării de comenzi.

Deci, când să nu folosiţi CQRS ?

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.

Fig. 2 - Implementarea noastră dedicată de CQRS

CQRS şi practicile Agile

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:

  • Un domain model care asigură regulile de business.
  • Un model pentru citire care e optimizat pentru ceea ce aplicaţia are nevoie să prezinte pe GUI.

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.

Despre securitate

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.

Riscuri

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.

Concluzii

CQRS este un şablon puternic datorită motivelor mai sus menţionate, pe care le vom reitera în cele ce urmează:

  • Permite scalabilităţi diferite pentru componentele de scriere şi citire.
  • Se bazează pe observaţii solide care arată că citirile sunt mult mai frecvente decât scrierile.
  • Permite paralelizarea efortului de dezvoltare pe două nivele.
  • Permite menţinerea unui domain model corect şi pur pe partea de procesare a comenzilor, care să nu fie afectat de cerinţele de interogare (acestea s-ar putea schimba relativ frecvent atât în timpul cât şi după implementarea aplicaţiei).
  • Permite folosirea tehnologiilor adecvate pentru procesarea comenzilor şi interogărilor, reducând astfel complexitatea aplicaţiei şi durata de dezvoltare şi îmbunătăţindu-i performanţa.
  • Reduce complexitatea aplicaţiei prin separarea responsabilităţilor la nivel macro.
  • În anumite circumstanţe potenţează aplicarea şablonului domain-driven design (care este în sine un aspect foarte important).
  • Permite folosirea diferitelor mecanisme de persistenţă precum event sourcing , care este foarte potrivit în anumite cazuri.

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ă!

Conferință

Sponsori

  • comply advantage
  • ntt data
  • 3PillarGlobal
  • Betfair
  • Telenav
  • Accenture
  • Siemens
  • Bosch
  • FlowTraders
  • MHP
  • Connatix
  • UIPatj
  • MetroSystems
  • Globant
  • Colors in projects