Command-Query Responsibility Segregation – Egy érv az Azure Table Storage mellett?

A június 15.-i azure klub keretében előkerült a SQL Azure vs Table Storage kérdés, a kettő között felállítható párhuzam (ha létezik egyáltalán ilyen), valamint egyáltalán a NoSQL megoldások létjogosultságának és használhatóságának kérdése. Akik ott voltak a találkozón emlékezhetnek, hogy valódi érveket nehezen sikerült felhoznunk a Table Storage mellett, inkább csak megérzéseket: ha a megoldásunk nem igényli a relációs adatbázisok által nyújtott szolgáltatásokat, ám szükség van valamilyen perzisztálásra. E gondolat mentén elindulva jutottam el a „Command-Query Responsibility Segregation (CQRS)”-ig, még inkább a témával foglalkozó blog oldalakig.

Ezen oldalakról összeszedett gondolatokat rendszereztem kicsit, melyek teljes megértéshez mindenképpen szükséges a CQRS legalább átfogó ismerete, amiről innen kiindulva itt olvashattok, ám ezen ismeretek nélkül is értelmezhető.

A CQRS mögötti legegyszerűbben megfogható szándék, hogy szeparáljuk az adatok megváltoztatását (command) az adatok lekérdezésétől (queries). Ezen egyszerű döntés a klasszikus alkalmazás architektúrákra nézve magával hoz pár változtatást és lehetőséget. Melyek eredményeképp a CQRS kultusz valójában fejlesztési elvek, minták és ajánlások együttese, mellyel extrémen skálázható és robosztus, magas üzleti értékkel rendelkező alkalmazásokat tervezhetünk.

Elévült adatok

Általános, hogy alkalmazásainkban több szereplő (konkrét felhasználó, vagy automatizált folyamat) párhuzamosan fér hozzá írásra/olvasásra adatok ugyanazon köréhez. Elévült adatról (Stale Data) beszélünk akkor, ha miután az egyik szereplőnek megmutattunk egy információt, a lekérdezést követő pillanatban egy másik szereplő ezt információt megváltoztatta. Így tehát nem feltétlenül bízhatunk az első szereplő döntéseiben (módosításaiban), hiszen azokat olyan információkra alapozta melyek közben elévültek.

Standard architektúráinkban e problémát legtöbbször explicit nem is kezeljük, helyette relációs adatbázisba pakoljuk adatainkat, és rá bízzuk a konkurenciakezelést. A CQRS ezzel szemben azt állítja, hogy az adatoknak mindig aktuálisnak (staleness) kell lennie (legalábbis közel aktuálisnak), így explicit módon szükséges kezelni a konkurencia kérdését. Akkor miért is van szükségünk tranzakciós adatbázisra?

Lekérdezések (Queries)

Adatainkat a relációs adatbázisokban n.-ik (n>=2) normálformában tároljuk, majd ezeket képezzük le az üzleti modellünkre. Az adatokon kívül másra nincs szükségünk, így felesleges a relációs adatbázisok minden megszorítása és tárolási szabálya. Majd ezen üzleti modellt tovább transzformáljuk DTO-kra (Data Transfer Objects), hogy hálózaton keresztül továbbíthatóak legyenek. Elég csak a WCF DataContract-jaira gondolnunk. Az MVC minta elterjedésével pedig modellünket végül leképezzük a „view model” objektumaira. Rengeteg (felesleges?) munka, mindez az újrahasználhatóság jegyében!

Készítsünk helyette egy extra adattárat, elvégre az adataink úgyis mindig aktuálisak! Miért ne lehetne eleve a view model reprezentálva ebben a tárban? Egy tábla minden view-hoz. Alkalmazásaink egy egyszerű „select * from myviewtable” lekérdezéssel megjeleníthetnék aktuális felületüket. Gondoljunk ebbe bele egy kicsit! Nincsenek relációk az egyes view model-ek között, a kapcsolatot maximum egy „where” feltételben megfogalmazott elsődleges kulcs jelenti, azaz nincs szükségünk a relációkra az adattárolás oldalán sem! Sőt, semmi akadálya, hogy akár több tárunk is legyen a lekérdezések számára, megteremtve az extrém skálázhatóság egy újabb eszközét!

Módosítások (Commands)

A CQRS egyik legfontosabb pontja a felhasználói felületek újragondolása. Módosításainkat parancsokba szervezve, a modellen történő közvetlen módosítás helyett, újra kell gondolnunk, hogy mely párhuzamos műveleteket engedjük meg és melyeket utasítjuk vissza. Példaként egy partner nevének, vagy címének módosítása minden további nélkül megtörténhet párhuzamosan, sőt ugyanazon partner státuszát is bátran módosíthatjuk. Minden parancs esetén explicit módon szabályozva döntünk arról, hogy mely további parancsok feldolgozását engedjük. Minél nagyobbak entitásaink, minél több attribútummal rendelkeznek, annál nagyobb az esélye, hogy a standard architektúrákban ütközésre kerül sor, ezáltal frissítésre és a művelet megismétlésére kényszerítve a felhasználót. A módosítások „command”-okba szervezésével akár azt is megtehetjük, hogy ugyanazon szereplő újabb módosítást kérjen mielőtt az előző sikerességét egyáltalán visszajeleztük. Egy módosítás sikertelensége esetén pedig egyszerűen értesítjük a felhasználót az adott parancs sikertelenségéről, és ő dönthet a szükséges lépésekről.

Mint láthattuk a parancsainkat nem szükséges azonnal feldolgozni, várakozási sorba (Queue) pakolhatóak. A feldolgozás sebessége innentől nem architektúrális, hanem „SLA” kérdése. Mi több, parancsainkat kategorizálva külön-külön várakozási sorok alakíthatóak ki. Nagyobb terheltség esetén csak a szükséges parancsfeldolgozók számának növelésére van szükség.

Mivel módosításainkat ilyen módon külön tároljuk a lekérdezésektől és a modelltől, több lehetőség is megnyílik előttünk. Tárunkhoz való hozzáférés optimalizálását explicit módon szabályoztuk, csökkentve a rendszer érzékenységét a tár hibáira, a felhasználónak nem is kell értesülnie a felmerülő problémákról. Nincs is szükségünk a relációs adatbázisoktól megszokott tranzakcionalitásra, deadlock kezelésre.

Az üzleti modell

A CQRS elvei nagy hatással vannak alkalmazásaink üzleti modelljére is. Semmi sem követeli meg tőlünk, hogy különböző parancsainkat ugyanazon üzleti modellen értelmezzük. A másik oldal pedig, hogy immár nem az üzleti modellünkön fogalmazzuk meg a lekérdezéseket, így nincs szükség az egyes entitások közötti relációk nagy számosságára. Modellünk szeparálhatóvá válik, az egyes részek akár eltérő implementációval is megadhatók. A modellünkön értelmezett ilyen
egyszerűsítések bevezetésével az „aggregate root” entitások relációi akár meg is szüntethetőek. Senki sem fogja a modellünkön lekérdezni egy partner rendeléseit (Partner 1 – * Orders), a kapcsolatok egyszerű ID kapcsolatokra vezethetők vissza, melyek szükség esetén feltölthetőek, hisz a felhasználói felület megjelenését nem lassítják. Ezt az ID-t pedig a parancsaink hordozzák.

Ilyen formában parancsaink egyetlen entitáson értelmezhetőek lesznek, a relációs adatbázisok tábla
szerkezete is fölöslegessé válhat, valós alternatívává téve számunkra a felhő platformok kulcs-érték típusú table storage megoldásait.

Felhő plattform

Alkalmazásaink újragondolásával és a CQRS alkalmazásával a választott technológiáktól sokkal függetlenebbül
koncentrálhatunk tisztán az üzleti igényekre, úgy hogy élvezzük az alábbi előnyöket:

  • Közel végtelen skálázhatóság
  • Komplex problémák is modellezhetőek a fejlesztési komplexitás növekedése nélkül
  • Más rendszerekkel való integráció jóval szabadabb foka
  • Robosztus, hibatűrő és tervezetten konkurens alkalmazás
  • Kiaknázhatjuk a felhő platform nyújtotta szolgáltatások előnyeit

Egy lehetséges referencia cloud platformra szánt architektúra az alábbiakból építkezhet:

  • CQRS
  • DDD
  • Üzenet alapú architektúra, Message Queuing
  • Service Bus és Event Sourcing
  • NoSQL tárolás, Azure Storage

Természetesen ez nem jelenti azt, hogy felhőre minden alkalmazást így éri meg elkészíteni, mindig az aktuális követelmények határozzák meg a szükséges architektúrát és implementációs technológiát, ám remélem a lehetőségek egy újabb halmazát sikerült ezzel a kis gondolatébresztővel számotokra megvilágítani.

Forrásanyagok:

Ha valakinek akár a CQRS-el kapcsolatban, akár a CQRS és a felhő platformok lehetséges előnyeivel és hátrányaival kapcsolatban kérdése van, esetleg érdekelné példa architektúra és implementáció az írjon bátran a megjegyzések közé, vagy keressen email-ben az ersek.attila@hotmail.com címen.

5 thoughts on “Command-Query Responsibility Segregation – Egy érv az Azure Table Storage mellett?

  1. Érdekes írás. Még kicsit emészteni kell a linkeket, de elsőre úgy tűnik, hogy a CQRS jó érzékkel ott támad, ahol a baj van – segít megszabadulnunk a gigamodellektől. Vagy nem, de egyelőre még csak a bevezetőnél tartok 🙂

    • Saját olvasatom szerint ez inkább egy lehetséges eredménye, és nem célja a CQRS-nek, de valóban elérhető vele. Szolgáltatás orientált architektúrákban amúgy sem szeretjük az elhízott modelleket. Amire nehéz ráérezni, az a staleness és a magas aszinkronitás így nem állítanám, hogy a nagyon adat orientált alkalmazások (data driven) minden komponense megtervezhető így, de implementációs kérdéssé alacsonyítja az üzleti model meglétét és az “application facade”-et.

      • Na pont ezek azok, amiket körbe kell olvasnom – igaziból az üzleti modellt és az application facadet nem szükséges rossznak, implementációs kérdés szinten kezelhető dolognak tartom, hanem az architektúra alapvetéseinek, amik még mindig fájtak, ha megpróbáltam őket figyelmen kívül hagyni tervezéskor. Van egy olyan érzésem, hogy a CQRS ezeket nem kezeli azzal a súllyal amit én tulajdonítok nekik, de meg akarom érteni, miről szól, mielőtt véleményem lesz róla.

  2. Visszajelzés: CQRS « delZubu blog

  3. Helló,

    Amit az “Elévült adatok” részben leírsz, én egy kicsit másképpen értelmezem: arról van szó, hogy legtöbb rendszerben, ami adat feljön, az “nem friss” (stale – állott). Lehet, hogy 1 perces, lehet, hogy csak 10 másodperces, de ez elég arra, hogy esetleg megváltozzon. Ha a rendszer nem olyan, hogy azonnal frissíti a megjelenített adatokat (gondolom pl egy tőzsdei rendszer igyekszik ilyen lenni) vagy pedig lockolja a megjelenített adatokat, akkor fel kell készíteni arra, hogy stale adatok alapján indítanak egy műveletet. Ha pedig úgyis fel kell készíteni, akkor miért ne lehetne ezt kihasználni?
    Tehát a CQRS nem azt mondja, hogy az adatok mindig aktuálisak, hanem pont azt mondja, hogy ha úgysem friss az adat, és ezzel számolni kell, akkor miért ne lehetne lekérdezésekhez eleve cache-elt (stale) adatokat használni? És ebből jönnek az előnyök, mint pl lekérdezésekhez nem kell túráztatni az adatbázist, eleve más szerkezetben (a megjelenítést könnyebben lehetővé tévő formában) lehet tárolni az adatokat, stb.

Hozzászólás a(z) Tóth Viktor bejegyzéshez Kilépés a válaszból