CQRS – pre veľa komplikovaných dát

Zdeno Jašek

13.08.2018

Command and Query Responsibility Segregation (CQRS) je návrhový vzor, ktorý odporúča implementovať dva rôzne modely pre ten istý pojem v závislosti od účelu: jeden model pre zápis dát („command“) a druhý model pre čítanie dát („query“). Špecializované modely umožňujú architektom naplno využiť silu objektového prístupu aj v komplikovaných doménach nad veľkým množstvom dát. Tento článok vysvetľuje, kedy sa CQRS využíva, aké sú technologické možnosti jeho implementácie a prípadné riziká a problémy s ním súvisiace.

Situácia pre použitie CQRS

Častokrát sa stáva, že v centre doménového modelu leží trieda reprezentujúca kľúčový pojem domény s týmito vlastnosťami:

  1. Samotná trieda je zložitá
  2. Trieda má veľa ďalších súvislostí a priamych asociácií na iné triedy
  3. Nad triedou je definovaná zložitá biznis logika
  4. V systéme existuje veľa inštancií danej triedy
  5. Používateľ vyžaduje silné vyhľadávacie nástroje

Obvykle ide o kľúčový pojem domény – napr. „Faktúra“ vo fakturačnom systéme, „Vyšetrenie“ v ambulantnom softvéri, „Zákazník“ v systéme pre evidenciu zákazníkov apod.

Vytvoriť jeden model na pokrytie všetkých uvedených požiadaviek je nesmierne náročné. Zatiaľ čo prvé tri požiadavky vedú ku košatému, zložitému a flexibilnému objektovému modelu, požiadavky 4 a 5 vytvárajú tlak na model optimalizovaný pre rýchle vyhľadávanie dát.

CQRS prístup spočíva v myšlienke, že ak nie je možné vytvoriť jeden model, treba vytvoriť modely dva: jeden na zadávanie dát a vyhodnocovanie komplexných biznis pravidiel („command model“) a druhý model optimalizovaný na čítanie dát („query model“).

Príklad CQRS

V ambulantnom softvéri by mala trieda „Vyšetrenie“ referencovať pacienta. Súčasťou vyšetrenia môžu byť predpísané recepty, identifikované diagnózy, dátum vyšetrenia, dekurz atď. Všetky uvedené pojmy súvisia s pojmom „Vyšetrenie“.

(Model je zjednodušený – viacero atribútov by malo referencovať ďalší typ namiesto využitia Stringu.)

Lekár bude určite potrebovať dobrý nástroj na vyhľadávanie v zozname vyšetrení. Napríklad môže potrebovať nájsť 30-ročného pacienta, ktorému zapísal diagnózu „D53.2“ a predpísal liek s názvom „STADAMET“ (príklad je náhodný). Dotazy do databázy by prehľadávali viacero navzájom prepojených tabuliek a v prípade veľkého objemu výkonov (napríklad pre polikliniku) by mohli trvať rádovo aj minúty.

SQL databázy ponúkajú rôzne nástroje na optimalizáciu vyhľadávania (indexy, partitioning), pričom obvykle vyžadujú istú mieru denormalizácie dát. Optimálna tabuľka pre vyhľadávanie vyšetrení môže vyzerať takto:

Dátový model iba naznačuje niektoré prístupy ako je napr. denormalizácia dát. Neobsahuje všetky dáta z prvého modelu.

Uvedené dva modely demonštrujú odlišný pohľad na dáta. Prvý model preferuje flexibilitu a podporu pre implementáciu doménovej logiky, druhý model je vyladený pre rýchle dopyty na dáta.

Implementácia CQRS

CQRS nepredpisuje využitie microservices, messages, event-sourcingu či NoSQL databázy. CQRS hovorí len o tom, aby architekt zvážil použiť dva modely tam, kde to dáva zmysel.

Možné implementácie CQRS:

  1. Databázový SELECT
  2. Databázový VIEW
  3. Samostatná tabuľka
  4. NoSQL model

Databázový SELECT

Najjednoduchšou implementáciou myšlienky CQRS je využitie databázového SELECTu na prístup k dátam, ktoré do databázy vložila objektová vrstva. Čítanie dát teda obchádza objektovo-relačný mapovací nástroj (Hibernate, EclipseLink), aby mal programátor priamu kontrolu nad vykonávaným SELECT-om. Z načítaných dát sa nevyrábajú doménové objekty, ale dáta sa rovno vrátia ako návratová hodnota služby.

Pri použití CQRS je dôležité neustále dbať na to, aby skutočne všetky zápisy prebiehali cez command model a všetky čítania cez query model. Vkladanie príliš veľkej doménovej logiky do priameho prístupu k databáze znamená, že doménová logika odíde do SQL príkazov.

Pristupovať k dátam cez SELECT-y je síce najjednoduchší prístup, ale súčasne zápasí s mnohými obmedzeniami: slabými možnosťami optimalizácie dát a častým prepadávaním doménovej logiky do SQL príkazov.

Databázový VIEW

Databázový VIEW je veľmi podobný prístup ako v prípade SELECT-u s podobnými obmedzeniami. Výhodou použitia VIEW je zhmotnenie query modelu priamo na úrovni databázy. Akékoľvek zmeny do dátovej časti „command modelu“ sú lepšie udržiavané, pretože databáza sama signalizuje narušenie view.

Samostatné tabuľky / samostatná databáza

Najväčšia sila CQRS prístupu sa prejaví pri využití samostatnej tabuľky (alebo viacerých tabuliek). Tabuľka pre query-model môže byť optimalizovaná na čítanie dát, takže dokáže vyťažiť z databázy maximálny výkon. Komplikáciu však predstavuje údržba dát. Každý update databázy v command modeli treba preniesť do query modelu:

Prenášanie dát z command modelu do query modelu sa môže diať buď synchrónne alebo asynchrónne. Synchrónny spôsob je síce spoľahlivejší, ale má negatívny dopad na rýchlosť zapisovania dát a navyše vytvára úzku väzbu medzi oboma modelmi. Asynchrónny spôsob nebrzdí aplikáciu, ale prináša problémy eventuálnej konzistencie a celkovej nekonzistencie pri zlyhaní. Obvykle sa využíva asynchrónny prístup práve kvôli lepšiemu výkonu.

Asynchrónnu aktualizáciu dát v query modeli je možné založiť na spracovávaní doménových udalostí, ktoré vytvára command model. Doménová udalosť nesie v sebe správu o vzniku situácie dôležitej z pohľadu domény. Udalosťou môže byť napríklad založenie vyšetrenia pacienta, zmena vyšetrenia alebo ukončenie vyšetrenia. Každá z uvedených udalostí nesie v sebe údaje o vyšetrení. Command model teda doménové udalosti vytvára, zatiaľ čo Event handler ich spracúva a na ich základe ukladá dáta do databázy pre query model. Z technologického pohľadu je vhodné doménové udalosti implementovať ako správy – napr. pomocou JMS (java message service).

Používanie CQRS v najsilnejšej implementácii – t.j. cez samostatnú tabuľku (alebo tabuľky) je náročné a prináša viaceré problémy v súvislosti s konzistenciou dát:

  1. Čo ak databázová transakcia v command modeli dáta zapíše, ale doménová udalosť sa nepošle?
  2. Čo ak sa doménová udalosť príjme, ale nespracuje?
  3. Čo ak služba zapíše dáta v command modeli a súčasne ich chce načítať z query modelu, kam však ešte nedorazili?
  4. Čo ak údaje aktualizujú viacerí používatelia a správy vo fronte zmenia poradie?

NoSQL model

CQRS nijako nepredpisuje, že query model musí byť postavený na SQL databáze. V niektorých doménach môže byť výhodnou implementáciu query modelu objektový model, graf alebo dokumentová databáza. Aktualizácia dát prebieha rovnako ako pri samostatnej tabuľke.

Záver

Zložitejšie modely nie je možné dostatočne dobre optimalizovať na čítanie a zápis súčasne. Udržanie oddelených modelov pre „command“ a „query“ prináša možnosť optimalizovať model z pohľadu jeho účelu. Nevyhnutnou daňou za používanie dvoch modelov namiesto jedného je réžia údržby konzistencie dát v oboch modeloch.

Ďalšie blog posty, čo by ťa mohli zaujímať