Files
2026-01-28 02:29:38 +01:00

330 lines
10 KiB
TeX

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