Architektura škálovatelných aplikací

Je to už nějaký pátek, co se zajímám o vzor CQRS (Command Query Responsibility Segragation), který nachází uplatnění především u dvou typů aplikací, které ale nejsou tak časté – velmi komplexní aplikace (díky CQRS se lze lépe vypořádat s komplexitou) a velmi velké a/nebo vytížené aplikace (které potřebují škálovat). Ačkoliv se takto může zdát znalost CQRS pro běžného programátora zbytečná (ne každý je Google), je s CQRS spjato mnoho velmi zajímavých konceptů, které mohou nalézt uplatnění i u běžných aplikací. Proto jsem se rozhodl sepsat z mého hlediska to nejzajímavější, co mě CQRS naučilo.

Klasická představa vrstvení aplikace je takováto:

V krabičce “Application” je skryta doménová logika i přístup k datům – odprostím se tedy prozatím od toho, jak je jádro aplikace implementované (DDD s ORM, Transaction Scripts, Active Record, …). Místo toho se zaměřme na to, jak je realizována komunikace mezi Aplikací a GUI – je to nějaká fasáda – vrstva rozhraní, které utváří API naší Aplikace, a nedělá tedy nic jiného, než že tupě volá Aplikaci.

Ve webovém GUI se striktně rozlišuje (a kdo to nedělá, je prasátko) mezi requesty, které provádí jen čtení (GET), a requesty jen měnícími stav (POST). Jak ví každý slušný webový vývojář, POST požadavek by neměl nic prezentovat (renderovat html) – měl by jen “nějak” zpracovat požadovaný příkaz a říci prohlížeči, kdo mu poskytne vizuální reprezentaci výsledku (tj. na jakou stránku má udělat redirect). Tento koncept je znám jako vzor Post-Redirect-Get a vzásadě to není nic jiného než krásná implementace CQSCommand Query Separation, což je koncept, který říká, že “objekt” by měl mít jen dva druhy metod:

  • provádí výhradně změnu a nic nevrací (tj. vrací void a nemají výstupní parametry)
  • nemění stav, jen čtou

Převedeno do řeči CQRS, GUI (obecně zbytek světa) komunikuje s Aplikací srkzeva Commands (první typ metody) & Queries (druhý typ metody):


Šipky ukazují směr toku informace.

Commands

Když chceme po Aplikaci něco vykonat (provést nějakou akci, která změní stav), pošleme směrem k Aplikaci tzv. Command. To je obyčejná třída bez metod, jen krabička na data (DTO), jejíž properties představují parametry příkazu, který chceme provést.

Commandové API Aplikace je triviální – je to jediný interface, který má jednu metodu: void HandleCommand(ICommand command)
V zájmu minimalizace requestů se často přidává druhá metoda HandleCommands, jejíž účel si laskavý čtenář jistě domyslí 😉
Toto univerzální rozhraní má jediný úkol (často delegovaný na kontejner) – předat Command do odpovídajícího Command Handleru, který provede jeho zpracování (ten už komunikuje přímo s Aplikací).

public interface ICommandHandler<TCommand> where TCommand: ICommand
{
  void Handle(TCommand command);
}

V čem je síla takového řešení? Protože pořád nikde nic nevracíme, nemusíme čekat na “fyzické” zpracování příkazu, tzn. GUI zobrazí obligátní hlášku “Váš požadavek bude zpracován.” a může vesele pokračovat dál.

Informace o zpracování

Zde vyvstává otázka, jak informovat uživatele/externí systém, že byl příkaz (ne)úspěšně zpracován (pokud ho to zajímá). Protože Command Handlery nic nevrací, musíme použít jinou komunikační cestu – v případě uživatele na e-shopu nás nepřekvapí klasické poslání mailu. Jiný způsob (pokud chceme informovat uživatele interaktivněji) je ten, že ICommand obsahuje property Id typu Guid, kterou může GUI nastavit a na základě tohoto identifikátoru požadavek trackovat. Konkrétní způsoby realizace jsou mimo rozsah toho článku, ale pěkným řešením je notifikace webového serveru o zpracování Commandu skrze Message Bus a následně nějaká forma (pseudo)persistentního spojení mezi webovým serverem a prohlížečem (pollování, WebSockets, …).

Benefity

Přijde vám posílání Commandů zbytečně složité a nestojí podle vás za to, že máme díky němu možnost zadávat příkazy z GUI asynchronně? Souhlasím! Síla tohoto řešení je totiž v tom, že nám umožní rozložit zátěž v čase (tj. rozmělnit špičky v zátěži) a řešit výpadky aplikace. Těchto neocenitelných benefitů docílíme tím, že GUI nebude Commandy posílat přímo naší aplikaci, ale bude je jen předávat prostředníkovi (Message Bus, Command Bus, Message Queue), který je bude dále přeposílat naší aplikaci.
Pokud zrovna nebude naše aplikace dostupná, tak uživatel v GUI nedostane chybovou hlášku, ale požadavek (Command) se uloží do fronty, a jakmile aplikace naskočí, prostředník to zmerčí, přepošle Command aplikaci a ta ho zpracuje. Uživatel tak dostane mail s potvrzenou objednávkou maximálně o chvilku později a není obtěžován momentálních chvilkovým výpadkem aplikace.

Onen prostředník (MessageBus) by pro nás měla být (podobně jako databáze) jen infrastrukturní záležitost (100% dostupná) a typicky ji nebudeme implementovat sami, ale použijeme nějaké existují řešení – v případě vlastní infrastruktury např. MSMQ nebo RabbitMQ, v případě cloudu např. Azuří Service Bus nebo Amazoní Simple Queue Service.

Kompatibilita

Když se na tento způsob interakce GUI a Aplikace podíváme s odstupem, zjistíme, že to není o tolik složitější než klasický přístup. Místo každé hlavičky metody budeme mít třídu reprezentující Command a tělo metody přesuneme do metody Handle odpovídající Command Handleru (nic nebrání, aby jedna třída implementovala více podobných Command Handlerů).

public OldFashionedFacade: IOldFashionedFacade
{
  public void OrderProduct(Product product, Customer customer)
  {
    // handle using Application
  }
}

public class OrderProductCommand : ICommand
{
  public Product Product { get; set; }
  public Customer Customer { get; set; }
}

public class OrderProductCommandHandler: ICommandHandler<OrderProductCommand>
{
  public void Handle(OrderProductCommand command)
  {
     // handle using Application
  }
}

Za povšimnutí stojí to, že parametry staré metody přejdou v properties třídy reprezentující Command. Pokud chceme mít nadále dostupné old-fashioned API (např. si nemůžeme dovolit měnit takto zásadně API), můžeme jednoduše nagenerovat toto API z Commandů. Jednoduše pro každý Command vygenerujeme jednu metodu fasádního interface, properties Commandu překlopíme na parametry této metody a metoda bude vevnitř jen volat univerzální Handle. Ale ani tady se nemusíme zastavit – můžeme např. nagenerovat i metody se sufixem Sync, které zajistí, že Command bude okamžitě zpracován místo zařazení do fronty v MessageBusu (někdy se to může hodit).

Jak vidno, zadávání požadavků do Aplikace přes Commandy nám přináší neuvěřitelné možnosti a v tomto řešení vidím jediný problém – je těžké rozhodnout, který kód je součástí Aplikace (a měl by tedy být uvnitř Aplikace) a který kód je jen infrastrukturní (a měl by tedy být v Command Handleru). Rozhodně do Command Handleru patří konverze typů, základní validace a autorizace. Zbytek je na vašem rozhodnutí…

Queries

Dotazovací část fasády Aplikace zůstane zvenku prakticky stejná – pořád se bude jednat o synchronní volání metod, protože uživatel chce vidět produkty v gridu okamžitě. Navenek to tedy mohou zůstat klidně fasádní služby, které budou volat NĚCO vespod. Pokud jste čekali, že to NĚCO bude Aplikace, musím vás zklamat. Prezentovat uživateli data, která prošla skrze Doménovou vrstvu, to bývá jeden z největších problémů nejen z hlediska výkonu (načtete celou entitu a pak zobrazíte jen příjmení) tak z hlediska čistoty kódu – pokud jste např. přidali entitě Person readonly property DisplayName jen proto, že ji potřebujete v GUI, tak si rovnou dejte facku 😉

Takže co bude to, co budou volat metody fasádních služeb (Queries) ? Nejefektivnější je sahat přímo do databáze – v případě .NET přes ADO.NET, příp. nějaký jednoduchý mapper (např. Dapper). Ať se vám to může při prvním pohledu zdát jakkoliv zvrhlé, po několika měsících musím uznat, že je to dobrá cesta a nevede k masivní duplikaci kódu, jak by to mohlo na první pohled vypadat.

Jak vidíme, de facto teď máme nad jednou persistentní vrstvou dvě aplikace – jedna se stará o zpracování Commandů a druhá o co nejrychlejší prezentaci dat uživateli (via Queries). Queries teď dělají to, že spouští (vysoce optimalizované) dotazy nad databází, které tahají jen ta data, která jsou opravdu potřeba.

Když budeme mít ale jen jednu databázi a aplikace zrovna bude pod velkým write-loadem (hodně Commandů), odnesou to Queries vyšší latencí – proto může být dobrý nápad rozdělit databázi na dvě:

OpDB (Operational Database) je běžná databáze v 3NF+, ke které Aplikace přistupuje běžným způsobem – např. pomocí full-featured ORM – v .NET se dá za něj považovat asi jen NHibernate, Entity Framework teprve bloumá okolo nádraží 😉

K ReadDB přistupují Queries co nejpříměji, např. přes nějaký zmiňovaný lightweight-ORM. Účelem této databáze je umožnit servírovat data uživateli co nejrychleji. Nejrychlejší by bylo mít pro uživatele předrenderovanou celou stránku (HTML), jen ji přečíst na jeden request z databáze (přes Query, resp. ReadApp) a přes webový server poslat do prohlížeče. To je poněkud extrémní řešení, proto se spokojme s tím, že budeme schopni na jeden request (a řekněme do desítek ms) získat všechna data potřebná pro zobrazení celé stránky, tedy “ViewModel” stránky. ViewModel můžeme v databázi mít uložen jako Json, XML, DTO serializované do BLOBu, jak je libo. Jediná podmínka by měla být, že jsme schopni získat všechna data na jeden jednoduchý dotaz (žádný join!).

Správně, ReadDB není nic jiného než předem napopulovaná cache, po které nechceme nic jiného než vytáhnout surová data na základě nějakého ID. Proto ReadDB nemusí být vůbec klasická relační databáze – může to být klidně NoSQL databáze! A naše aplikace pak bude cool! 😉

Synchronizace

Je nabíledni, že musíme ReadDB průběžně upravovat podle toho, jak se mění OpDB (~synchronizovat). To se typicky děje (opět) pomocí Message Busu, kdy při úpravě Doménového modelu vyvoláme událost, že došlo k nějaké změně (ProductChanged). Kdokoliv (tedy včetně ReadApp) má pak šanci tuto událost odchytit a poupdatovat podle ní odpovídající předpřipravené ViewModely apod.

Pokud ale nechceme udělat takový velký skok v používaných technologiích, není nic ztraceno. Můžeme uvažovat o dvou databázích jen z konceptuálního hlediska, fyzicky budeme mít jen jednu klasickou relační databázi. Jak už jsem psal, na úplném začátku jsme v situaci, kdy v Queries píšeme dotazy nad OpDB. Abychom dosáhli rozumného výkonu, musíme i v tomto řešení začít duplikovat data – k tomu slouží v klasických relačních databázích indexy. Takže máme databázi s indexy, klasika.

Prvním krokem k možnému budoucímu přesunu na více fyzických databází je to, že vytvoříme pohledy (Views). Prakticky sice budeme dělat pořád to samé, ale dotazy už budou ve své finální podobě (“SELECT * FROM ViewModel1 WHERE Id = @Id“) a nebudeme na ně tak muset v dalších krocích sahat.

Další iterací je to, že pohledy budou materializované. Pokud to náš DB stroj neumí, můžeme si je implementovat sami – jednoduše vytvoříme místo pohledů tabulky se stejnou strukturou. Pak si vytvoříme triggery nad původními tabulkami a podle zápisů do nich budeme upravovat naše materializované pohledy.
Je zřejmé, že se nám tím zpomalí zápis, ale čtení bude bleskurychlé – takže konkrétní řešení závisí i na tom, jaký je ve vaší aplikace poměr čtení/zápis (nejčastěji to prý bývá 80 % vs. 20 %).

Pokud budeme chtít ReadDB a OpDB skutečně fyzicky rozdělit (a pokud neumíme zajistit spouštění triggerů přes více DB strojů), musíme synchronizaci přenést o úroveň výše. A tím se dostáváme zpět k výše zmiňované synchronizaci přes události (Events), které můžeme vyvolávat tam, kde to bude pro naši aplikaci nejvhodnější – v DALu, v Doméně nebo třeba po úspěšném zpracování Commandu…

UI

Jak je vidět z posledního obrázku, naše GUI teď prakticky pracuje nad dvěma aplikacemi – jedna zajišťuje načítání dat a druhá zpracování dat. Načítání dat zůstává prakticky stejné jako v případě klasické (non-CQRS) aplikace, ale rozdíly ve zpracování dat jsou markantní. Už nejsme schopni garantovat to, že po odeslání objednávky ji zákazník hned uvidí v nějakém seznamu, protože třeba příkaz ještě není zpracován nebo ještě nestačila proběhnout synchronizace ReadDB. A tomu je třeba přizpůsobit koncepci uživatelského rozhraní, příp. si více pohrát s aplikací. V části o Commandech jsem se např. zmiňoval o možnosti trackování stavu Commandu…

V souvislosti s CQRS se často mluví o tzv. Task-Based UI, což je alternativa k běžnému CRUD UI. Není to něco, co je v CQRS povinné, ale považuji to za zajímavý koncept, proto ho chci letmo zmínit.
Klasické CRUD UI se vyznačuje tím, že umožňuje vytvořit entititu, upravit ji a příp. ji smazat. Problém je, že když voláme metodu Change, tak tím vůbec nevyjadřujeme záměr, proč tak činíme.
Bylo by mnohem lepší, kdychom měli připravené Commandy, které by vyjadřovaly, že např. měníme adresu zákazníka, protože se stěhuje (a podle nové adresy mu doporučit novou výchozí prodejnu), že měníme příjmení zákazníka, protože se oženil/vdala/registroval(a), že mažeme zákazníka, protože zemřel atd.
A pro tyto specifické tasky pak budeme mít specifické UI – Task-Based UI.
S problematikou ukládání takových informací do databáze souvisí Event-Sourcing, ale o tom třeba někdy jindy…

Co z toho?

CQRS je velmi zajímavý vzor, díky kterému jsem se dozvěděl o spoustě zajímavých konceptů, z nichž některé úspěšně používám v praxi. Ty nejzajímavější koncepty (MessageBus, ReportingDatabase) jsem se pokusil nastínit v tomto článku a věřím, že vás mohou inspirovat k dalšímu studiu CQRS a příp. i vylepšení vašich aplikací.

O CQRS není problém vygooglit spoustu materiálů (a možná se vás už ani Google nebude ptát “Did you mean CARS?” ;-)) – doporučuji především materiály od Grega Younga, Udiho Dahana a Rinata Abdullina.


Příspěvek byl publikován v rubrice Programování a jeho autorem je Augi. Můžete si jeho odkaz uložit mezi své oblíbené záložky nebo ho sdílet s přáteli.

26 komentářů u „Architektura škálovatelných aplikací

  1. Díky za dobrý článek.

    Jedna z věcí, co mě u škálování příjde zajímavá a o které obecně nacházím málo informací, je konzistence typu “read your own writes”.

    Když budu programovat nějakou velkou aplikaci, třeba sociální síť, tak je mi celekem jedno, že se kamarád doví o komentáři k jeho fotce “asynchronně” – rozuměj “o něco pozdějc” :-). Na druhou stranu když si já sám změním profilovou fotku a dám refresh browseru, je opravdu hodně blbé tu informaci mít pro čtení zrovna ještě starou.

    Setkal jsem se se situací, kdy si server štosoval frontu pro pozdější zpracování, nicméně mohla přijít situace, kdy jsem potřeboval aktuální data. Pak jsem po frontě chtěl přednostně (okamžitě) zpracovat data nějak “otagovaná”, typicky idčkem uživatele, který do ní záznam přidal. Ale tak nějak chápu, že to vlastně porušuje celý princip asynchronní fronty…

  2. Výborný článek Augi, díky.
    Jen by mě zajímalo.
    1)U některých aplikací nemám problém s vyzvednutím dat. Podle mě jde o jiný “bouded context?; když použiju terminologii DDD. Ve stejném duchu jsem mluvil o přímém použiti datasetu/readeru pro reporty/naplnění gridu. Klícové je, že vždy musí jít o data pouze pro čtení, přičemz tato separace Read/Write operací je jádrem CQRS.

    2) Podle mě je největší problém tohoto přístupu v udržení aktuâlních “view modelů”-zdrojů dat pro query. Pro spoutu aplikací je ta eventuální konzistence nebo masivní využití triggerů/mesagingu problematické.

    3) Objekty Command považuju za tenké wrappery nad logikou aplikace. Snažím se v nich držet minimum kódu.

    4) Event sourcing, který jsi nakousl , je podle mě skvělý na jednu věc. Dovoluje zrekonstruovat z přijatých událostí stav systému k libovolnému datu. V aplikaci, kde jsem event sourcing musel použít kvůli složitým požadavkům na historii, bylo ale nutné kromě podpory pro záznam a opakované zpracování události vybudovat komplexní infrastrukturu pro verzování aplikace i databáze (zjednodušeně – u každé události se ví, proti jaké verzi aplikace a databáze je možné ji spustit, a pokud verze db a/nebo app nesouhlasí, dochází při generování alternativní historické verze aplikace k upgradu db a/nebo app).

    Psáno na HTc Desire, omluv prosím překlepy.

  3. Dík za super článek.

    Mám stejnej dotaz, jako René nakousl svou otázkou 2: Jak zjistíš, že “ReadDB” není aktuální? Jak zjistíš který data synchronizuješ po výpadku aplikační vrstvy? A co když na chvíli vypadne ReadDB?

    Když máš např. 2 webový servery a chceš přidat třetí, jak naplníš jeho ReadDB?

    PS: Event sourcing mi přijde jako regulární výrazy (Máte problém a chcete ho řešit regulárními výrazy? Tak to máte problémy dva:-)

  4. @Michal Můžeš klidně podvádět, jako to dělá gmail, facebook i některý blogenginy s komentářema – pomocí javascriptu uživateli ihned ukážeš výsledek, jako kdyby se jeho akce už vykonala. Předpokládá se, že command bude na 99% úspěšný, když dojde k chybě, uživatel se o ní dozví asynchronně.

  5. Asi zase budu za trolla, ale nu což. Doufám, že jsi strávil osm hodin nad epickým blogem proto, aby ses něco naučil. 🙂

    V článku vidim pouze jedinou zmínku o DDD a to ještě v závorce s poznámkou, že se od tohoto detailu odproštuješ. CQRS dává smysl právě s DDD. Pokud děláte CRUD, nemá CQRS hlubší význam, ale je to pak spíš technologická masturbace.

    Dále v článku pod jedním vzorem mícháš hned několik různých konceptů (CQRS, Command Pattern, Messaging, Event Sourcing), které spolu můžou dávat smysl, ale nejsou na sobě vůbec závislé a téma pak opravdu získává na komplexitě, ikdyž jde o velmi jednoduché koncepty.

    Post-Redirect-Get není stříbrnou kulkou a označovat ostatní přístupy za prasečinku je trochu silné. 🙂 HTTP specifikace říká jasně, jak se chovat, jaké vracet statusy a data, ale o HTTP 302 tam zmínka neni. Je to spíš takové hacky řešení (to neznamená, že ho nepoužívám 🙂 ). Lepší ukázka CQR v MVC aplikaci je užití dvou různých modelů pro vstup a výstup akce (InputModel/ViewModel) namísto reusování jednou entitního modelu napříč aplikací.

    Jak něco, co dělá z jednoho rozhraní dvě, může snižovat komplexitu? 🙂

    CQR je strašně jednoduchý vzor. Dobře jsi popsal druhy metod, ale už jsi zapomněl zmínit (nebo jsem to nikde nenašel), že tyto metody mají být od sebe izolované. Na místo toho abys měl službu s queries i commandy, budeš mít služby dvě. Jednu pouze s queries a druhou pouze s commandy. A díky tomu, že ve většině systémů se řádově více čte, než zapisuje (tuším, že u tebe to je asi obráceně), můžeš query služby instancovat samostatně na více strojích a commandy mít třeba jen na dvou (kvůli dostupnosti).

    Pěknou adventní neděli přeji 🙂

  6. @Aleš Ad oddělení služeb: mě žádná “query služba” nedává smysl. Čtení dat by mělo fungovat lokálně, nebo přímým přístupem do “ReadDB” cache. Tzn v ideálním případě bude mít každý web server svoji cache lokálně. Ale jak psal René, chybí zde popis “bounded contexts”, ve kterém to dává větší smysl.

  7. @Aleš ehm… Ne. Proč bych měl pro čtení dat pro webovou stránku potřebovat jakoukoliv servisu? Myslím fyzickou servisu, ne jen konceptuální oddělení na jednom tieru.

  8. Protože může jít jak o konceptuální oddělení na jednom tieru (pak nemá CQRS cenu řešit), tak o detaily (jako UI) oproštěný pohled ve smyslu SOA, tam CQRS význam má. Je důležitý si uvědomi, že CQRS je tu kvůli škálování, ne kvůli koncepčnímu oddělování a zbytečnému vrstvení, jen proto, abychom dělali architekturu.

    Většina webů jsou CRUDy, kde si vystačíš s ActiveRecordama, a škálování nemusíš řešit. Oproti tomu webové služby, jako třeba AdWords mají obrovský load jak pro servření reklam, tak pro reportingy přes SOAP a samotnou webovou aplikaci. V kontrastu k tomu je zadávání reklam do systému, které se dělá jen v nepatrném poměru k dotazům.

    Každá featura v produkci znamená náklad na správu a potenciální výskyt chyby. Pokud dotazovací a výkonnou část striktně oddělíš (CQRS), pak můžeš ušetřit a každou část optimalizovat zvlášť. Ale to už jsou detaily, ke kterým CQRS otevírá bránu, ale z principu je neřeší.

  9. Michal Till: Jak už psal Dan, řešil bych to nějakým podvodem na straně webového serveru. Určitě se ti už někdy stalo, že jsi něco napsal na Twitteru, tvůj tweet se okamžitě objevil, ale za pár sekund ti shora vyjela chybová hláška 😉

    René: 3) To já taky 🙂 Většinou tam je jen nějaké mapování, příp. jednoduchou validaci, zbytek (autorizace, základní validace) řeším přes AOP.

    Dan: V CQRS se všude předpokládá, že ReadDB je stále Stale 🙂
    Když vypadne ReadDB, tak zprávy pro ni zůstanou vyset v MessageQueue.

    Plnění další ReadDB: ReadDB je jistá forma cache a i v případě webové cache bych se rozhodoval, jestli použít pro každý WebServer samostatnou cache nebo použít nějakou společnou distribuovanou. Osobně bych se klonil spíš k druhé variantě (ale záleží, co si tam člověk strká).

    Podobně bych se rozhodoval i u ReadDB – raději bych použil nějakou “distribuovanou” databázi, která umí dobře škálovat (třeba Cassandru ;-)) a všechny WebServery by se pak dotazovaly do clusteru.

    Pokud bych měl použít nějakou “autonomní” databázi, musel bych si sharding implementovat sám a použil k tomu odpovídající techniky. Můžeš třeba použít to, že novou ReadDB napopuluješ tak, že když se někdo bude na něco ptát a ty to nebudeš vědět, tak se zeptáš služebně starší ReadDB (a při příštím dotazu už budeš vědět). To je ale IMHO dost hacky řešení a raději bych se mu vyhnul…

  10. Troll:
    V článku vidim pouze jedinou zmínku o DDD a to ještě v závorce s poznámkou, že se od tohoto detailu odproštuješ. CQRS dává smysl právě s DDD. Pokud děláte CRUD, nemá CQRS hlubší význam, ale je to pak spíš technologická masturbace.
    Ohledně tohodle si můžeš jít trollovat třeba k Udimu na blog (který tvrdí to samé co já) 😉
    Nepleteš si to s Task-Based UI? To má opravdu smysl dělat jen proti “behaviorálnímu” modelu, což je právě DDD…

    Dále v článku pod jedním vzorem mícháš hned několik různých konceptů (CQRS, Command Pattern, Messaging, Event Sourcing), které spolu můžou dávat smysl, ale nejsou na sobě vůbec závislé a téma pak opravdu získává na komplexitě, ikdyž jde o velmi jednoduché koncepty.
    Mně zase přijde špatné stavět CQRS, Messaging atd. na stejnou úroveň. CQRS beru jen jako návod, jak použít různé “koncepty” dohromady tak, aby to dobře fungovalo. A krása je v tom, že člověk nemusí použít všechny koncepty, ale jen některé, které u daného projektu dávají smysl.
    Někdo (myslím, že Greg Young) v jednom článku dokonce tyhle různé varianty CQRS pojmenoval…
    Polemika na téma “Co přesně znamená CQRS” by jen nafoukla už tak dlouhý článek…

    Lepší ukázka CQR v MVC aplikaci je užití dvou různých modelů pro vstup a výstup akce…
    Já jsem se ale nechtěl omezovat na popis CQS v (ASP.NET) MVC – chtěl jsem, aby to bylo srozumitelné pro co nejvíce lidí a proto jsem zvolil příklad, ve kterém operuji jen s HTTP. Navíc mi přišla pro další výklad vhodnější “behaviorální” varianta CQS než “strukturální”.

    CQR je strašně jednoduchý vzor. Dobře jsi popsal druhy metod, ale už jsi zapomněl zmínit (nebo jsem to nikde nenašel), že tyto metody mají být od sebe izolované.
    A ten obrázek, kde jsou Commandy a Queries v samostatných krabičkách jsi viděl? 😉
    Se zbytkem odstavce souhlas – jen jsem opět nechtěl čtenáře přehltit informacemi…

  11. Řekněme, že se n CQRS dá koukat podobně jako na HTML5. Ale nemám tohle rozlejzání a zobecňování rád. 🙂 Pro mne je CQRS velice jednoduchý vzor, který umožňuje škálování. Různé přístupy a detaily do něj nezahrnuju.

  12. Do záplavy fundovaných komentářů přispěju možná trochu trapným dotazem, ale nedá mi to. Píšeš:

    pokud jste např. přidali entitě Person readonly property DisplayName jen proto, že ji potřebujete v GUI, tak si rovnou dejte facku
    Já to tedy občas použiju (ano, naliskal jsem si, až mám hubu modrou), co je podle tebe řešením? Zkonvertovat to do metody? Tak to dělám běžně, ale třeba u WPF občas neodlám, protože to notně usnadní práci (pravda, tam většinou binduju pouze k ‘prázdným’ dataholderům, tak to tolik nebolí).

  13. Jiří: Něco jako “DisplayName” by dle mého názoru mělo být tam, kam to patří – a to je prezentace. Proto bych to rozhodně nedával do “core” objektů v aplikační logice.

    Záleží v jaké technologii zobrazuješ. Pokud je to třeba ASP.NET MVC, tak by za to mohla být zodpovědná DisplayTemplate, příp. nějaký Helper.
    Pokud prezentuješ pomocí WPF, tak konkrétní radu bohužel nedám, ale mohl by to být nějaký ViewModel reprezentant osoby…

  14. Jasně, “readDB” neboli “cache” může bejt jedna sdílená pro všechny servery ve webový vrstvě, ale pokud se bavíme o škálování, pak úplně nejlíp škáluje “shared nothing”. Dalším argumentem by mohlo být, že UI a “reporting service” budou mít velmi pravděpodobně odlišný ViewModel, a tudíž cache ani sdílet nepotřebují. Když se pak i aplikace rozdělí na jednotlivé “autonomous business components”, pak společná cache nedává moc smysl. Nechci tvrdit že je jedna varianta lepší nebo horší, já jen že takhle to tlačí Udi Dahan.

    No a moje původní otázka byla, jak se při tomhle setupu řeší invalidace a repopulace cache. Vono i když nic nespadne, tak stačí přidat do modelu sloupec, jak píše Jiří, a některé ViewModely jsou rázem nekompletní.

  15. Dalsou vyhodou Commandov oproti fasadnym metodam je lahka priamociara implementacia historie prikazov a ‘Undo’, ale to asi nie je uplne pripad webovych aplikacii.

  16. Ahoj,

    Moc zajímavý článek – věřím, že při sepisování to muselo dát spoustu práce.

    Nyní ve frimě pracujeme na projektu, při kterém tuto architekturu vážně zvažujeme.

    Zajímalo by mě, zda se dá na netu dohledat nějaké jednoduché funkční řešení v .netu této architektury.

  17. Super článek,

    možná jsem do této doby blbě hledal, ale konečně vidím článek na českém webu, který seznamuje čtenáře s CQRS. Takže díky za něj a více takových.

    Mennion:
    Mně příjde jako jeden z nejlepších zdrojů o CQRS, např.:
    https://github.com/SzymonPobiega/DDDSample.Net
    což je vlastně původní implementace DDD příkladu, kterou uvadí Evans ve své knížce, doplňená o ruzné variace implementace CQRS.

    Dále další výborný zdroj informací a příkladů je např.:
    https://github.com/MarkNijhof/Fohjin
    bližší popis k jeho aplikaci najdeš na jeho blogu, případně zde detailnější pohled na jednu z možných implementací CQRS jako architektury, využívající například i event sourcing
    http://elegantcode.com/2009/11/11/cqrs-la-greg-young/

    Ale pokud chceš opravdu úplně jednoduchou věc využívající principy CQRS, tak např. Greg Young má skvělou ukázku
    https://github.com/gregoryyoung/m-r
    ale to ber spíše s nadsázkou

    A teď trochu z jiného soudku. Je to spíše otázka na ostatní. Osobně mě zajímá nakolik lidi v praxi používají DDD a jeho principy jako bounded context(k nim spjaté ubiquitous language), aggregates(společně s aggregates roots) atd.

    Já se tyto principy teprve učím, a většinou mi dělá problém nalezení správných aggregate roots. Takže výsledek je pak takový, že se zbytečně zvyšuje komplexita programu.

    Tak mě zajímájí různé rady, tipy jak se vypořádat s problémy při návrhu aplikace využívající principy DDD.
    A myslím, že je to i pěkný námět na článek(např. s nějakým konkrétním příkladem z praxe, pokud to jde):)

    Vím, že už jsi tu psal o architektuře podle DDD, ale to bylo spíše obecné povídání.

  18. Jura :

    Díky za linky, zvlášť ten z elegantcode je parádní.

  19. @Ales:
    Aj ked s vacinou Tvojich vyhrad suhlasim.

    “CQRS dává smysl právě s DDD.”

    Ja by som to skor povedal tak, ze CQRS otvara dvere na aplikaciu DDD a pomaha prekonavat problemy, na ktore ludia v praxi narazili pri aplikacii DDD.

    Ja vidim problem v tom, ze CQRS je velmi uzitocny pattern, ktory pomoze cloveku sa oslobodit mentalne od zazitej architektury aplikacie a umozni mu ju prisposobit specifikam projektu. Ten pattern priniesli ludia, ktori dokazali rozmyslat a nehladali univerzalne riesenie.
    Ale to neplati vzdy o ludoch, ktori dalej siria tento pattern. Navyse cast z nich ma nimimalne alebo ziadne skusenosti s aplikovanim CQRS v praxi – na vacsich komercnych projektoch. Zial, co je mozno chyba aj Graga, casto sa CQRS prezentuje v tej najrozvinutejsej forme (s Event Sourcingom) – co je zase ale pochopitelne, lebo tie prednasky su adresovane urcitej skupine ludi, zvacsa architektov velkych rieseni(nie garazovym web kutilom)
    Lenze zial, cast ludi zacne implementovat mega architekturu so zapojenim ES zase ako dogmu bez ohladu na to, aky su specifika ich projektu(popalia sa na tom a rezignuju na DDD) a cast ludi sa nad CQRS ani nezamysly, lebo pre nieco taketo nevidia dovod.

    Na margo toho, kedy je mozne profitovat z CQRS:
    Problem ktory vidim je, ze sa dost cast prezentuje v tom najrozvinutejsom zmysle (EventSourcing) a ludia nevidia profit, ktory mozu mat uz pri mensej aplikacii napr. s ORM. Projekty s ORM casto vykapu na velkom objektovom grafe pri queryovani a zaroven trpia komplexitov a internymi zavislostami, ktore sa tazko manageuju. Aplikacia CQRS znamena v tomto pripade oddelit read a write pristupy. Reporting teda vyriesim tak, ze pouzijem napr. db views a nejake lightweight ORM na ziskanie reportingovych dto like objektov a na write pouzivam spokojne dalej ORM. Vysledkom je, ze som sa naopak zbavil komplexity, viem ovela lepsie profitovat z ORM, pri reade profitujem zo sily DB pre querovanie a pri write casti zase mozem profitovat z 2nd cache.

    Este jedna myslienka – Co specificky bounded context(z pohladu DDD) to kludne iny pristup a model.(niekde mozem profitovat z eventsourcingu, niekde chcem len snapshot). To je nieco co nebolo vyslovene ale je na pozadi CQRS s DDD ako dalsia oslobodzujuca myslienka.

    Sorry za dlhsi prispevok (hlavne @augi).

  20. @Augi:
    Na clanku sa mi pacia najma dve veci – idea postupneho/mozneho prechodu od shared DB pre R a W ku separovanym uloziskam aj uvedomenie si moznosti pouzit dve odlisne db technologie pre R a W.
    Skoda, ze to je na konci clanku a snazil si sa tam popisat prilis vela patternov, konceptov a problemov v zmysle toho, co pisal Ales. Ludi, ktori o tom nic nevedia to asi pomota.

    “Nejrychlejší by bylo mít pro uživatele předrenderovanou celou stránku (HTML), jen ji přečíst na jeden request z databáze”

    Takto by som nestaval ako dogmu. Screen moze obsahovat naraz niekolko typov informacii z roznych kontextov. Nemusim nasilu spajat napr. zoznam objednavok na vybavenie s nejakymi statistickymi informaciami, ja neviem, o pocte dnes uz vybavenych objednavok danym userom za dnes/tyzden/mesiac).

Napsat komentář

Vaše emailová adresa nebude zveřejněna.