Files
GKLB_INTM131/órai anyagok/week03/sections/03-cqrs.tex
T
2026-09-08 09:36:02 +02:00

116 lines
6.3 KiB
TeX

\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}