% CQRS Pattern \section{CQRS Pattern} \begin{frame}{Mi a CQRS?} \begin{block}{Command Query Responsibility Segregation} \textbf{Magyarul:} Parancs-Lekérdezés Felelősség Szétválasztása \vspace{0.3cm} Az adatok \textbf{írásának} (módosítás) és \textbf{olvasásának} (lekérdezés) szétválasztása külön modellekre. \end{block} \begin{columns} \begin{column}{0.48\textwidth} \begin{exampleblock}{Command (Parancs)} \textbf{Írási műveletek:} \begin{itemize} \item POST, PUT, PATCH, DELETE \item Adatok módosítása \item Validáció, üzleti logika \item Pl: felhasználó létrehozása \end{itemize} \end{exampleblock} \end{column} \begin{column}{0.48\textwidth} \begin{exampleblock}{Query (Lekérdezés)} \textbf{Olvasási műveletek:} \begin{itemize} \item GET \item Adatok lekérdezése \item Optimalizált olvasás \item Pl: felhasználók listázása \end{itemize} \end{exampleblock} \end{column} \end{columns} \end{frame} \begin{frame}{Miért van szükség CQRS-re?} \begin{block}{A probléma} Hagyományos CRUD alkalmazásokban ugyanazt a modellt használjuk írásra és olvasásra. Ez problémás lehet: \begin{itemize} \item Az írási és olvasási igények eltérőek \item Az olvasás gyakran bonyolultabb (join-ok, aggregációk) \item A teljesítmény-optimalizálás nehéz \end{itemize} \end{block} \begin{exampleblock}{A megoldás: CQRS} Külön modellek külön optimalizálással: \begin{itemize} \item \textbf{Write Model} (Író modell): validáció, üzleti szabályok \item \textbf{Read Model} (Olvasó modell): gyors lekérdezések, cache \end{itemize} \end{exampleblock} \end{frame} \begin{frame}{CQRS előnyei} \begin{block}{Előnyök} \begin{itemize} \item \textbf{Független optimalizálás} - Az írás és olvasás külön optimalizálható \begin{itemize} \item Íráshoz: tranzakciók, validáció, konzisztencia \item Olvasáshoz: cache, denormalizált adatok, gyors lekérdezések \end{itemize} \item \textbf{Skálázhatóság (Scalability)} - Read és Write modellek külön skálázhatók \begin{itemize} \item Több olvasó szerver, kevesebb író szerver \end{itemize} \item \textbf{Egyszerűség} - Komplexitás csökken \begin{itemize} \item Minden modell egy feladatra fókuszál \end{itemize} \item \textbf{Teljesítmény (Performance)} - Gyorsabb lekérdezések \end{itemize} \end{block} \end{frame} \begin{frame}{Mikor használjuk a CQRS-t?} \begin{exampleblock}{Jó választás, ha:} \begin{itemize} \item \textbf{Komplex domain logika} van az alkalmazásban \item \textbf{Nagy terhelés} várható (sok párhuzamos felhasználó) \item \textbf{Eltérő írás/olvasás igények} \begin{itemize} \item Pl: ritkán írunk, sokszor olvasunk \end{itemize} \item \textbf{Különböző adatformátumok} kellenek íráshoz és olvasáshoz \end{itemize} \end{exampleblock} \begin{alertblock}{NEM használjuk, ha:} \begin{itemize} \item Egyszerű CRUD alkalmazás \item Kis terhelés \item Az írás és olvasás hasonló \end{itemize} \end{alertblock} \end{frame} \begin{frame}[shrink=10]{CRUD vs CQRS - Architektúra összehasonlítás} \begin{center} \begin{tikzpicture}[node distance=1.5cm] % CRUD \node[draw, rectangle, fill=blue!20, minimum width=2.5cm, minimum height=0.9cm, align=center] (crud) {\small CRUD\\Modell}; \node[above=0.3cm of crud, font=\bfseries] {Hagyományos}; \node[draw, rectangle, fill=green!20, minimum width=2.5cm, minimum height=0.9cm, right=1.2cm of crud, align=center] (db1) {\small Adatbázis\\(Database)}; \draw[<->, thick] (crud) -- (db1) node[midway, above, font=\tiny] {olvas/ír}; % CQRS \node[draw, rectangle, fill=yellow!20, minimum width=2.5cm, minimum height=0.9cm, below=2cm of crud, align=center] (cmd) {\small Parancs\\(Command)}; \node[draw, rectangle, fill=orange!20, minimum width=2.5cm, minimum height=0.9cm, below=0.6cm of cmd, align=center] (query) {\small Lekérdezés\\(Query)}; \node[above=0.3cm of cmd, font=\bfseries] {CQRS}; \node[draw, rectangle, fill=green!20, minimum width=2.5cm, minimum height=0.9cm, right=1.2cm of cmd, align=center] (db2) {\small Író DB\\(Write)}; \node[draw, rectangle, fill=green!20, minimum width=2.5cm, minimum height=0.9cm, right=1.2cm of query, align=center] (db3) {\small Olvasó DB\\(Read)}; \draw[->, thick] (cmd) -- (db2) node[midway, above, font=\tiny] {ír}; \draw[<-, thick] (query) -- (db3) node[midway, above, font=\tiny] {olvas}; \draw[->, dashed, thick] (db2) -- (db3) node[midway, right, font=\tiny] {szinkron}; \end{tikzpicture} \end{center} \end{frame} \begin{frame}[fragile,shrink=15]{Command - Írás példa} \begin{exampleblock}{CreateUserCommand} \tiny \begin{verbatim} // commands/create-user.command.js class CreateUserCommand { constructor(name, email, password) { this.name = name; this.email = email; this.password = password; } validate() { if (!this.email || !this.name || !this.password) { throw new Error('All fields required'); } if (this.password.length < 8) { throw new Error('Password too short'); } } } module.exports = CreateUserCommand; \end{verbatim} \end{exampleblock} \end{frame} \begin{frame}[fragile,shrink=15]{Command Handler} \begin{exampleblock}{CreateUserCommandHandler} \tiny \begin{verbatim} // handlers/create-user.handler.js const userRepository = require('../repositories/user.repository'); class CreateUserCommandHandler { async handle(command) { command.validate(); // Üzleti logika const existingUser = await userRepository.findByEmail(command.email); if (existingUser) { throw new Error('Email already exists'); } // Password hash const hashedPassword = await bcrypt.hash(command.password, 10); // User létrehozás const user = await userRepository.create({ name: command.name, email: command.email, password: hashedPassword }); return user; } } \end{verbatim} \end{exampleblock} \end{frame} \begin{frame}[fragile,shrink=15]{Query - Olvasás példa} \begin{exampleblock}{GetUsersQuery} \tiny \begin{verbatim} // queries/get-users.query.js class GetUsersQuery { constructor(filters = {}) { this.page = filters.page || 1; this.limit = filters.limit || 10; this.role = filters.role; this.active = filters.active; } } // handlers/get-users.handler.js class GetUsersQueryHandler { async handle(query) { // Optimalizált lekérdezés const users = await userReadModel.find({ role: query.role, active: query.active, skip: (query.page - 1) * query.limit, limit: query.limit }); return users; } } \end{verbatim} \end{exampleblock} \end{frame} \begin{frame}[fragile,shrink=15]{CQRS REST API-ban (1/2)} \begin{exampleblock}{Command végpontok} \tiny \begin{verbatim} // routes/user.routes.js const CreateUserCommand = require('../commands/create-user.command'); const createUserHandler = require('../handlers/create-user.handler'); router.post('/users', async (req, res) => { try { const command = new CreateUserCommand( req.body.name, req.body.email, req.body.password ); const user = await createUserHandler.handle(command); res.status(201).json(user); } catch (error) { res.status(400).json({ error: error.message }); } }); \end{verbatim} \end{exampleblock} \end{frame} \begin{frame}[fragile,shrink=15]{CQRS REST API-ban (2/2)} \begin{exampleblock}{Query végpontok} \tiny \begin{verbatim} const GetUsersQuery = require('../queries/get-users.query'); const getUsersHandler = require('../handlers/get-users.handler'); router.get('/users', async (req, res) => { try { const query = new GetUsersQuery({ page: req.query.page, limit: req.query.limit, role: req.query.role, active: req.query.active }); const users = await getUsersHandler.handle(query); res.json(users); } catch (error) { res.status(500).json({ error: error.message }); } }); \end{verbatim} \end{exampleblock} \end{frame} \begin{frame}{CQRS előnyei és hátrányai} \begin{columns} \begin{column}{0.48\textwidth} \begin{block}{Előnyök} \begin{itemize} \item Jobb teljesítmény \item Független skálázás \item Egyszerűbb modellek \item Optimalizált lekérdezések \end{itemize} \end{block} \end{column} \begin{column}{0.48\textwidth} \begin{alertblock}{Hátrányok} \begin{itemize} \item Komplexitás növekedés \item Több kód \item Szinkronizáció problémák \item Eventual consistency \end{itemize} \end{alertblock} \end{column} \end{columns} \end{frame} \begin{frame}{Event Sourcing + CQRS} \begin{block}{Event Sourcing} Az állapotváltozások eseményekként tárolása, nem a végállapot mentése. \end{block} \begin{exampleblock}{Események} \begin{itemize} \item UserCreatedEvent \item UserEmailChangedEvent \item UserDeactivatedEvent \end{itemize} \end{exampleblock} \begin{alertblock}{CQRS + ES kombinációja} Command-ok eseményeket generálnak, Query modellek eseményekből épülnek fel. \end{alertblock} \end{frame} \begin{frame}{Összefoglalás - CQRS} \begin{itemize} \item CQRS = Command és Query szétválasztása \item Command: adatok módosítása (POST, PUT, DELETE) \item Query: adatok lekérdezése (GET) \item Előnyök: teljesítmény, skálázhatóság \item Hátrányok: komplexitás \item Event Sourcing jól kombinálható CQRS-sel \end{itemize} \vspace{0.5cm} \end{frame}