\section{CQRS} \begin{frame}{A probléma: egy modell, két teljesen más terhelés} \footnotesize A legtöbb egyszerű alkalmazásban ugyanaz a kód, ugyanaz az adatmodell szolgálja ki mind az adatok módosítását (írás), mind a lekérdezését (olvasás). Ez sokáig teljesen jól működik -- de bizonyos rendszereknél ez a közös modell egyre inkább korlátozó tényezővé válik. \begin{itemize} \item Az írási műveleteknél fontos a szigorú validáció és az üzleti szabályok betartatása \item Az olvasási műveleteknél viszont gyakran \emph{más} adatszerkezet lenne ideális (pl. több táblából összefésült, "lapos" nézet egy listázó felülethez) \item Ha ugyanaz a modell szolgál ki mindkét célt, gyakran kompromisszumot kell kötni -- egyik oldal sem lesz igazán optimális \end{itemize} \begin{block}{Példa} Egy webshopnál a rendelés \emph{leadása} (írás) sok validációt igényel (készlet, fizetés, kedvezmények), míg a rendelések \emph{listázása} (olvasás) egyszerűen csak gyors, jól szűrhető megjelenítést igényel -- a kettő igénye nagyon eltérő. \end{block} \end{frame} \begin{frame}{A CQRS lényege} \footnotesize A \textbf{CQRS} (Command Query Responsibility Segregation, magyarul "parancs-lekérdezés felelősség szétválasztás") azt mondja ki: válasszuk szét élesen az írási (command) és olvasási (query) műveleteket -- akár külön modellel, külön kóddal, szélsőséges esetben külön adatbázissal is. \begin{itemize} \item \textbf{Command (parancs):} egy szándékot fejez ki valaminek a megváltoztatására (pl. "hozz létre egy rendelést") -- nem ad vissza adatot, legfeljebb egy azonosítót vagy sikerességet \item \textbf{Query (lekérdezés):} adatot kér le, \emph{nem} változtat semmin -- ez pontosan megfelel a HTTP GET idempotens, csak-olvasó jellegének, amit már az 1. héten megismertünk \end{itemize} \begin{alertblock}{Fontos megkülönböztetés} A CQRS nem ugyanaz, mint a HTTP metódusok GET/POST felosztása -- az egy protokollszintű konvenció, a CQRS pedig egy \emph{architekturális döntés} arról, hogy a kódunk belső szerkezete is kövesse ezt a felosztást. \end{alertblock} \end{frame} \begin{frame}[fragile]{Command és Query -- egyszerű példa} \footnotesize A lényeg pszeudokódban jól látszik: a Command egy műveletet hajt végre és validál, a Query csak lekérdez -- a kettő felelőssége, sőt akár a visszatérési típusa is más. \begin{columns} \begin{column}{0.48\textwidth} \begin{block}{Command -- írás} \begin{lstlisting}[language=JavaScript,style=NodeJS] function createOrder(data) { validate(data); checkStock(data.items); const order = save(data); return order.id; } \end{lstlisting} \end{block} \end{column} \begin{column}{0.48\textwidth} \begin{block}{Query -- olvasás} \begin{lstlisting}[language=JavaScript,style=NodeJS] function getOrderById(id) { return db .orders .findOne({ id }); } \end{lstlisting} \end{block} \end{column} \end{columns} \begin{block}{Mi változik igazán CQRS-sel?} Nem feltétlenül a fájlok fizikai elválasztása a lényeg, hanem az, hogy a Command sosem ad vissza lekérdezési adatot, és a Query sosem módosít állapotot -- ez a szigorú szétválasztás teszi átláthatóvá a rendszert. \end{block} \end{frame} \begin{frame}{Miért éri meg a CQRS-t bevezetni?} \footnotesize A szétválasztás nem öncélú bonyolítás -- konkrét, mérhető előnyöket ad olyan rendszereknél, ahol az írási és olvasási terhelés jelentősen eltér egymástól. \begin{itemize} \item \textbf{Független skálázhatóság:} ha egy rendszerben sokkal több az olvasás, mint az írás (ez a leggyakoribb eset), az olvasási oldalt külön lehet skálázni \item \textbf{Optimalizált olvasási modellek:} a lekérdező oldalon szabadon kialakíthatunk a megjelenítéshez ideális, denormalizált nézeteket, anélkül hogy az írási oldal adatmodelljét kompromisszumba kényszerítenénk \item \textbf{Egyszerűbb, célzottabb kód:} egy Command csak a validációra és a módosításra koncentrál, egy Query csak a lekérdezésre -- egyik sem próbál "mindent tudni" \end{itemize} \end{frame} \begin{frame}{Mikor túlzás a CQRS?} \footnotesize A CQRS egy erős eszköz, de nem ingyen jön -- minden architekturális mintánál fontos feltenni a kérdést: \emph{a probléma, amit megold, valóban jelen van-e nálunk?} \begin{alertblock}{Ökölszabály} Egyszerű CRUD alkalmazásoknál (ahol az írás és olvasás igénye hasonló, és nincs komoly skálázási probléma) a CQRS csak felesleges réteget és bonyolultságot ad hozzá -- ilyenkor egy egyszerű, közös modell teljesen megfelelő. \end{alertblock} \begin{itemize} \item Kis csapatnál, kis projektnél a többletkód karbantartása nem éri meg a hasznot \item Csak akkor vezessük be, ha ténylegesen tapasztaljuk a "két különböző igény" problémáját \item A tárgy hátralévő részében épített API-nk egyszerű CRUD marad -- a CQRS itt főleg mint \emph{fogalom} és \emph{gondolkodásmód} fontos \end{itemize} \end{frame} \begin{frame}{Kitekintés: Event Sourcing} \footnotesize A CQRS-t gyakran együtt említik egy másik mintával, az \textbf{Event Sourcing}-gal, bár a kettő nem jár automatikusan együtt. \begin{block}{Az alapötlet} Ahelyett, hogy csak az adat \emph{jelenlegi állapotát} tárolnánk, minden változást (eseményt) elmentünk -- pl. "OrderCreated", "OrderPaid", "OrderShipped". A jelenlegi állapotot ezekből az eseményekből lehet bármikor újraszámolni. \end{block} Ez a tárgy keretein túlmutat, de érdemes tudni, hogy létezik: teljes körű auditnaplót ad ingyen, és jól illeszkedik a CQRS command-oldalához. Ára viszont jelentős többletkomplexitás. \end{frame} \begin{frame}{Összefoglalás -- CQRS} \footnotesize \begin{block}{CQRS} \begin{itemize} \item A probléma: egy közös modell néha rosszul szolgálja ki mind az írást, mind az olvasást \item CQRS: Command (írás, nem ad vissza adatot) és Query (olvasás, nem módosít) szigorú szétválasztása \item Előny: független skálázhatóság, optimalizált olvasási modellek, egyszerűbb, célzottabb kód \item Csapda: egyszerű CRUD alkalmazásoknál felesleges többletkomplexitás \item Kapcsolódó fogalom: Event Sourcing (események tárolása állapot helyett) -- ez már a tárgy keretein túlmutat \end{itemize} \end{block} \end{frame}