alapok modosit
This commit is contained in:
@@ -0,0 +1,329 @@
|
||||
% 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}
|
||||
Reference in New Issue
Block a user