third week

This commit is contained in:
Donát committed 2026-09-08 09:36:02 +02:00
1 parent 95e62c915f
commit a935851e22
31 files changed
+1269 -33

No files matched your search

@@ -2,7 +2,7 @@
\usepackage{../common/webfejl}
\title{Webtechnológia és webalkalmazás-fejlesztés}
\subtitle{2. hét -- REST API, Architekturális felépítések, CQRS}
\subtitle{2. hét -- Express.js, Routing, Postman, TypeScript}
\begin{document}
@@ -12,9 +12,10 @@
\end{frame}
\input{sections/00-visszatekintes}
\input{sections/01-rest-api}
\input{sections/02-architektura}
\input{sections/03-cqrs}
\input{sections/04-kovetkezo-lepesek}
\input{sections/01-expressjs}
\input{sections/02-routing}
\input{sections/03-postman}
\input{sections/04-typescript}
\input{sections/05-kovetkezo-lepesek}
\end{document}
@@ -2,30 +2,31 @@
\begin{frame}{Miről volt szó múlt héten?}
\footnotesize
Az első héten megismertük a webes kommunikáció alapköveit: a HTTP protokollt, a Node.js futtatókörnyezetet és az npm csomagkezelőt. Ezek nélkül nem tudnánk elindulni -- de önmagukban még nem elegendők egy jól használható backend rendszer megépítéséhez.
Az első héten megismertük a webes kommunikáció alapjait: a HTTP protokollt, a Node.js futtatókörnyezetet, és elindítottunk egy egyszerű szervert a beépített \texttt{http} modullal. Emellett megtanultuk, hogyan kezeljük npm-mel a projekt függőségeit.
\begin{itemize}
\item \textbf{HTTP/HTTPS:} Request-Response modell, állapotmentes protokoll, metódusok (GET, POST, PUT, PATCH, DELETE), státuszkódok
\item \textbf{Node.js:} szerveroldali JavaScript futtatókörnyezet, beépített \texttt{http} modul egy egyszerű szerver elindítására
\item \textbf{npm:} csomagkezelés, \texttt{package.json}, függőségek
\item \textbf{HTTP:} Request-Response modell, metódusok (GET, POST, PUT, PATCH, DELETE), státuszkódok
\item \textbf{Node.js:} \texttt{http.createServer} -- egy függvény, ami minden kérést kiszolgál
\item \textbf{npm:} \texttt{package.json}, csomagok telepítése, \texttt{npm scripts}
\end{itemize}
\begin{block}{A hiányzó darab}
Múlt héten a \texttt{http.createServer}-rel csak egyetlen, mindent kiszolgáló függvényt írtunk. Éles alkalmazásoknál ez gyorsan kezelhetetlenné válik -- szükségünk van egy \emph{tervezési logikára}, ami megmondja, hogyan szervezzük az útvonalainkat, adatainkat és a kódunk rétegeit.
A nyers \texttt{http} modullal minden egyes útvonalat, minden HTTP metódust és a kérés törzsének feldolgozását is kézzel, \texttt{if-else} ágakkal kellene megírnunk. Ez pár végpontnál még kezelhető, de gyorsan átláthatatlanná válik.
\end{block}
\end{frame}
\begin{frame}{Mi a célja a mai órának?}
\footnotesize
A mai anyag három, egymásra épülő témát dolgoz fel: hogyan tervezzünk jól használható API-kat, hogyan szervezzük a backend kódunkat rétegekbe, és hogyan válasszuk szét az írási és olvasási műveleteket, ha a rendszerünk ezt megkívánja.
A mai órán egy valódi keretrendszert vezetünk be, amivel a múlt heti kézi munkát nagyrészt kiváltjuk, majd megtanuljuk, hogyan teszteljük és hogyan típusozzuk a kódunkat.
\begin{itemize}
\item \textbf{REST API alapok} -- hogyan tervezzünk következetes, könnyen használható API-t
\item \textbf{Architekturális felépítések} -- hogyan szervezzük a backend kódunkat rétegekbe, hogy karbantartható maradjon
\item \textbf{CQRS} -- mikor éri meg szétválasztani az írási és olvasási műveleteket
\item \textbf{Express.js} -- a legelterjedtebb Node.js webes keretrendszer, ami elveszi a routing és a kérésfeldolgozás nagy részének terhét
\item \textbf{Routing} -- hogyan szervezzük az útvonalainkat, hogyan olvassuk ki a kérés adatait
\item \textbf{Postman} -- hogyan teszteljük a saját API-nkat anélkül, hogy frontendet kellene hozzá írnunk
\item \textbf{TypeScript} -- hogyan adjunk típusbiztonságot a JavaScript kódunkhoz
\end{itemize}
\begin{alertblock}{Fontos}
A mai témák még mindig \emph{tervezési} kérdések -- konkrét kódot Express.js-ben csak a jövő héttől írunk. Most a "miért így?" kérdésre keressük a választ, hogy a következő heti kód már értelmes döntéseken alapuljon.
Ma még mindig egyszerű, tárolás nélküli (memóriában tartott) példákkal dolgozunk -- az adatbázis-kapcsolat csak a 4. héten kerül képbe. Most a keretrendszerre és az eszközökre fókuszálunk.
\end{alertblock}
\end{frame}
@@ -0,0 +1,95 @@
\section{Express.js}
\begin{frame}[fragile]{Miért nem elég a nyers \texttt{http} modul?}
\footnotesize
Emlékezzünk vissza az 1. héten írt szerverre: egyetlen callback-ben kellett eldöntenünk, milyen útvonalra és milyen metódussal érkezett a kérés.
\begin{lstlisting}[language=JavaScript,style=NodeJS]
const server = http.createServer((req, res) => {
if (req.url === '/users' && req.method === 'GET') {
// ...
} else if (req.url === '/users' && req.method === 'POST') {
// ...
} else if (req.url.startsWith('/users/') && req.method === 'GET') {
// egy adott felhasznalo -- id kezzel kivagva az URL-bol
} // es igy tovabb...
});
\end{lstlisting}
\begin{alertblock}{A probléma}
Tíz-húsz végpontnál ez a lánc kezelhetetlenné válik: nincs beépített segítség a paraméterek kiolvasásához, a JSON törzs feldolgozásához, vagy az útvonalak csoportosításához.
\end{alertblock}
\end{frame}
\begin{frame}{Mi az Express.js?}
\footnotesize
Az \textbf{Express.js} a legelterjedtebb Node.js webes keretrendszer (web framework): a nyers \texttt{http} modulra épül, és eltünteti a kézi \texttt{if-else} láncokat egy sokkal kényelmesebb, deklaratív routing API-val.
\begin{itemize}
\item Egyszerű, minimalista -- nem kényszerít ránk egy előírt projektszerkezetet
\item Beépített segítség: útvonal-paraméterek, query stringek, JSON válaszok
\item Hatalmas ökoszisztéma: middleware-ek szinte minden problémára (autentikáció, naplózás, CORS, stb.)
\item Ez lesz a tárgy hátralévő részének elsődleges keretrendszere
\end{itemize}
\begin{block}{Fontos}
Az Express \emph{nem} helyettesíti a Node.js-t -- ráépül. Amit az 1. héten a \texttt{http} modulról tanultunk (request/response ciklus), az Express alatt is pontosan úgy működik.
\end{block}
\end{frame}
\begin{frame}[fragile]{Az első Express alkalmazás}
\footnotesize
Néhány sorból már működő szerverünk van -- hasonlítsd össze az előző dia \texttt{if-else} láncával!
\begin{block}{Telepítés}
\begin{verbatim}
npm install express
\end{verbatim}
\end{block}
\begin{lstlisting}[language=JavaScript,style=NodeJS]
const express = require('express');
const app = express();
app.get('/', (req, res) => {
res.send('Hello, Express!');
});
app.listen(3000, () => {
console.log('Szerver fut: http://localhost:3000');
});
\end{lstlisting}
\end{frame}
\begin{frame}[fragile]{A middleware fogalma}
\footnotesize
Az Express legfontosabb koncepciója a \textbf{middleware}: egy függvény, ami a kérés és a válasz között fut le, és vagy továbbengedi a folyamatot (\texttt{next()}), vagy lezárja azt egy válasszal.
\begin{lstlisting}[language=JavaScript,style=NodeJS]
app.use((req, res, next) => {
console.log(`${req.method} ${req.url}`);
next(); // tovabb a kovetkezo middleware-hez/route-hoz
});
app.use(express.json()); // JSON body-t olvaso middleware
\end{lstlisting}
\begin{itemize}
\item A kérések egy "csövön" (pipeline) haladnak végig, middleware-eken keresztül
\item \texttt{express.json()} nélkül a \texttt{req.body} üres lenne POST/PUT kéréseknél
\item Naplózás, autentikáció, hibakezelés -- mind middleware-ként valósul meg
\end{itemize}
\end{frame}
\begin{frame}{Összefoglalás -- Express.js}
\footnotesize
\begin{block}{Express.js alapok}
\begin{itemize}
\item Node.js-re épülő, minimalista webes keretrendszer
\item Kiváltja a kézi \texttt{if-else} útvonal-elágazásokat
\item \texttt{app.listen()} -- szerver indítása, ugyanaz mint a nyers \texttt{http}-nél
\item Middleware: \texttt{app.use()} -- a kérés-válasz "csövének" építőeleme
\item \texttt{express.json()} -- beépített middleware a JSON törzs feldolgozásához
\end{itemize}
\end{block}
\end{frame}
@@ -1,138 +0,0 @@
\section{REST API alapok}
\begin{frame}{Mi az API?}
\footnotesize
Az \textbf{API} (Application Programming Interface) egy megállapodás arról, hogyan kommunikálhat egymással két szoftverkomponens -- anélkül, hogy az egyiknek ismernie kellene a másik belső működését. Amikor a telefonodon megnyitod egy időjárás-alkalmazást, az alkalmazás egy API-n keresztül kér adatot egy távoli szervertől; neked (és az alkalmazásnak) fogalma sincs, hogyan tárolja a szerver az adatokat belül -- csak azt kell tudnia, milyen kérést küldjön, és milyen választ várhat.
\begin{itemize}
\item Egyértelmű, dokumentált "szerződés" a kliens és a szerver között
\item Elrejti a belső implementációs részleteket (adatbázis típusa, kód szerkezete, stb.)
\item Web API-k esetén jellemzően HTTP felett kommunikálunk -- ez az, amit az elmúlt órán megismertünk
\end{itemize}
\begin{block}{REST -- egy konkrét API-stílus}
A \textbf{REST} (Representational State Transfer) nem egy protokoll vagy szabvány, hanem egy architekturális stílus, amit Roy Fielding írt le 2000-ben a doktori disszertációjában. Ma a legelterjedtebb módja a web API-k tervezésének.
\end{block}
\end{frame}
\begin{frame}{Mitől "RESTful" egy API?}
\footnotesize
Fielding öt fő elvet fogalmazott meg, amiknek egy API-nak meg kell felelnie ahhoz, hogy igazán RESTfulnek nevezzük. A gyakorlatban a legtöbb "REST API"-nak nevezett rendszer csak részben követi ezeket -- de minél többet betartunk belőlük, annál kiszámíthatóbb és skálázhatóbb lesz az API-nk.
\begin{itemize}
\item \textbf{Kliens-szerver:} a kliens és a szerver különálló felelősséggel bír, egymástól függetlenül fejleszthetők
\item \textbf{Állapotmentesség (statelessness):} minden kérés önmagában tartalmaz minden szükséges információt -- ezt már az 1. héten megismertük a HTTP-nél
\item \textbf{Cache-elhetőség:} a válaszoknak jelezniük kell, hogy gyorsítótárazhatók-e (pl. \texttt{Cache-Control} header)
\item \textbf{Egységes interfész (uniform interface):} az erőforrásokat egységes módon, azonosítható URL-eken keresztül érjük el
\item \textbf{Rétegzett rendszer:} a kliens nem tudja (és nem is kell tudnia), hogy közvetlenül a szerverrel, vagy egy köztes rétegen (pl. load balancer, cache) keresztül kommunikál-e
\end{itemize}
\end{frame}
\begin{frame}[fragile]{Erőforrás-orientált URL tervezés}
\footnotesize
Az URL-ek \emph{erőforrásokat} (főneveket) azonosítsanak, ne \emph{műveleteket} (igéket) -- a műveletet úgyis a HTTP metódus fejezi ki.
\begin{block}{Rossz gyakorlat (igealapú URL)}
\begin{verbatim}
GET /getAllUsers
POST /createNewUser
POST /deleteUser?id=123
\end{verbatim}
\end{block}
\begin{block}{Jó gyakorlat (erőforrás-alapú, többes szám)}
\begin{verbatim}
GET /users # osszes felhasznalo
POST /users # uj felhasznalo letrehozasa
GET /users/123 # egy adott felhasznalo
DELETE /users/123 # felhasznalo torlese
\end{verbatim}
\end{block}
\end{frame}
\begin{frame}{CRUD $\to$ HTTP metódus megfeleltetés}
\footnotesize
Ez a táblázat gyakorlatilag a REST API tervezés "alaptörvénye" -- ha ezt következetesen betartod, az API-d azonnal ismerős és kiszámítható lesz bárki számára, aki már használt REST API-t.
\begin{tabular}{|l|l|l|}
\hline
\textbf{Művelet} & \textbf{HTTP metódus} & \textbf{Végpont példa} \\
\hline
Create (létrehozás) & POST & \texttt{POST /users} \\
Read (egy elem) & GET & \texttt{GET /users/123} \\
Read (lista) & GET & \texttt{GET /users} \\
Update (teljes) & PUT & \texttt{PUT /users/123} \\
Update (részleges) & PATCH & \texttt{PATCH /users/123} \\
Delete (törlés) & DELETE & \texttt{DELETE /users/123} \\
\hline
\end{tabular}
\begin{block}{Emlékeztető az 1. hétről}
GET, PUT, DELETE idempotensek -- többszöri hívás ugyanazt az állapotot eredményezi. A POST nem az.
\end{block}
\end{frame}
\begin{frame}{Richardson Maturity Model}
\footnotesize
Leonard Richardson egy szintekre bontott modellt alkotott, amivel megmérhető, mennyire "RESTful" valójában egy API. A legtöbb valós API a 2. szinten áll -- ez teljesen elfogadható, a 3. szint (HATEOAS) ritkán éri meg a többletbonyolultságot.
\begin{itemize}
\item \textbf{0. szint:} egyetlen végpont, minden művelet ugyanazon keresztül megy (pl. RPC-szerű, SOAP-hoz hasonló stílus)
\item \textbf{1. szint:} külön erőforrások, saját URL-lel, de a HTTP metódusokat még nem használjuk következetesen
\item \textbf{2. szint:} erőforrások + helyesen használt HTTP metódusok és státuszkódok -- \emph{ez a gyakorlatban elterjedt "REST API" szint}
\item \textbf{3. szint (HATEOAS):} a válasz linkeket is tartalmaz a kapcsolódó műveletekhez, így a kliensnek nem kell előre tudnia az összes URL-t
\end{itemize}
\end{frame}
\begin{frame}[fragile]{API verziózás}
\footnotesize
Az API-k idővel változnak -- új mezők, módosított viselkedés. Ha egy változás nem visszafelé kompatibilis, szükség van egy módra, hogy a régi klienseket ne törjük el. Erre szolgál a verziózás.
\begin{block}{URL-alapú verziózás (leggyakoribb)}
\begin{verbatim}
GET /api/v1/users/123
GET /api/v2/users/123
\end{verbatim}
Egyszerű, jól látható, könnyen tesztelhető Postmanben is.
\end{block}
\begin{block}{Header-alapú verziózás}
\begin{verbatim}
GET /api/users/123
Accept: application/vnd.myapp.v2+json
\end{verbatim}
"Tisztább" URL-eket eredményez, de nehezebb böngészőből tesztelni.
\end{block}
\end{frame}
\begin{frame}[fragile]{Lapozás, szűrés, rendezés}
\footnotesize
Amikor egy erőforrásból (pl. felhasználók) több ezer is lehet, nem küldhetjük vissza mindet egyszerre -- ezt oldják meg a query paraméterek, amikkel a kliens finomíthatja, pontosan mit szeretne kapni.
\begin{verbatim}
GET /users?page=2&limit=20
GET /users?role=admin
GET /users?sort=-createdAt
GET /users?search=alice
\end{verbatim}
\begin{itemize}
\item \textbf{Lapozás (pagination):} \texttt{page}/\texttt{limit} vagy \texttt{offset}/\texttt{limit} párokkal
\item \textbf{Szűrés (filtering):} egy adott mező szerinti szűkítés (\texttt{role=admin})
\item \textbf{Rendezés (sorting):} \texttt{sort=mezonev}, mínusz előjellel csökkenő sorrend
\end{itemize}
\end{frame}
\begin{frame}{Összefoglalás -- REST API}
\footnotesize
\begin{block}{REST API tervezés}
\begin{itemize}
\item API: szerződés két rendszer kommunikációjához; REST: egy elterjedt architekturális stílus rá
\item Fielding elvei: kliens-szerver, statelessness, cache-elhetőség, egységes interfész, rétegzettség
\item URL-ek: erőforrások (főnevek, többes szám), nem műveletek (igék)
\item CRUD $\to$ HTTP metódus: POST/GET/PUT/PATCH/DELETE következetesen
\item Richardson Maturity Model: a legtöbb API a 2. szinten van, ez rendben van
\item Verziózás: URL vagy header alapon; lapozás/szűrés/rendezés: query paraméterekkel
\end{itemize}
\end{block}
\end{frame}
@@ -1,125 +0,0 @@
\section{Architekturális felépítések}
\begin{frame}{Mi a szoftverarchitektúra, és miért számít?}
\footnotesize
A szoftverarchitektúra azt írja le, hogyan van a kódunk nagy egységekre (rétegekre, modulokra, szolgáltatásokra) felosztva, és ezek hogyan kommunikálnak egymással. Egy kis, egyszemélyes prototípusnál szinte mindegy, hova írjuk a kódot -- de amint a projekt nő, csapatban dolgozunk rajta, vagy hónapok múlva kell hozzányúlni, a jó architektúra dönti el, hogy karbantartható marad-e a rendszer, vagy "spagettikóddá" válik.
\begin{itemize}
\item Könnyebb megérteni: világos, hogy melyik kód mit csinál, és hol keresendő egy adott logika
\item Könnyebb tesztelni: a rétegek külön-külön, elszigetelten tesztelhetők
\item Könnyebb bővíteni: új funkció hozzáadásakor tudjuk, hova illesztjük be
\item Könnyebb csapatban dolgozni: a felelősségek szét vannak választva
\end{itemize}
\end{frame}
\begin{frame}{Rétegzett architektúra -- az öt réteg (1/2)}
\footnotesize
A rétegzett felépítésnél a kódot vízszintes rétegekbe szervezzük, ahol minden réteg csak a közvetlenül alatta lévővel kommunikál. Ezen a tárgyon a következő öt réteges felosztást fogjuk következetesen használni -- elsőként a "felső" három réteg:
\begin{block}{}
\begin{itemize}
\item \textbf{API / Controller:} fogadja a HTTP kéréseket, validálja a bemenetet, meghívja az Application réteget, visszaadja a választ -- ezt Express.js-ben írjuk meg a jövő héten
\item \textbf{Application:} az egyes use case-eket (parancsokat/lekérdezéseket) orchesztrálja -- ide kerülnek majd a CQRS command/query handlerek
\item \textbf{Domain:} a tiszta üzleti logika, entitások, aggregates -- semmilyen technikai függősége nincs, nem "tudja", hogy Express-ben vagy adatbázissal fut
\end{itemize}
\end{block}
\end{frame}
\begin{frame}{Rétegzett architektúra -- az öt réteg (2/2)}
\footnotesize
A maradék két réteg a rendszer "peremén" helyezkedik el: az egyik a kifelé irányuló adatszerződéseket, a másik a technikai megvalósítást fogja össze.
\begin{block}{}
\begin{itemize}
\item \textbf{Communication:} a rétegek/külső rendszerek közti szerződések -- DTO-k, mapperek (6. hét)
\item \textbf{Infrastructure:} a konkrét technikai megvalósítás -- adatbázis-kapcsolat, külső szolgáltatások
\end{itemize}
\end{block}
\begin{alertblock}{Miért éri meg?}
Ha lecseréljük az adatbázisunkat, csak az Infrastructure réteget kell módosítani -- a Domain és az API réteg változatlan marad.
\end{alertblock}
\end{frame}
\begin{frame}{MVC minta}
\footnotesize
Az MVC (Model-View-Controller) a rétegzett architektúra egyik legismertebb konkrét megvalósítása, eredetileg felhasználói felületekhez, de a backend világban is elterjedt elnevezés lett a hasonló felosztásra.
\begin{itemize}
\item \textbf{Model:} az adatokat és az azokhoz tartozó szabályokat képviseli (nálunk: a Domain + Infrastructure réteg)
\item \textbf{View:} a megjelenítés -- backend API-nál ez jellemzően a JSON válasz formázása
\item \textbf{Controller:} fogadja a bejövő kérést, meghívja a megfelelő Model-műveletet, és visszaküldi a View-t
\end{itemize}
\begin{block}{Backend kontextusban}
Amikor a jövő héten Express.js-ben "route handler"-t írunk, az gyakorlatilag az API/Controller réteg szerepét tölti be: fogadja a HTTP kérést, és delegálja a munkát az Application rétegnek.
\end{block}
\end{frame}
\begin{frame}{Monolit vs mikroszolgáltatás}
\footnotesize
Ez a döntés arról szól, hogy a teljes backend rendszerünket egyetlen, összefüggő alkalmazásként (monolit), vagy sok, önállóan futtatható kis szolgáltatásra bontva (mikroszolgáltatások) építjük-e fel. Egyik sem "jobb" alapból -- a projekt méretétől és a csapat helyzetétől függ.
\begin{columns}
\begin{column}{0.48\textwidth}
\begin{block}{Monolit}
\begin{itemize}
\item Egyetlen kódbázis, egyetlen futó folyamat
\item Egyszerűbb fejleszteni és deployolni kisebb csapatban
\item Egyszerűbb tranzakciókezelés
\end{itemize}
\end{block}
\end{column}
\begin{column}{0.48\textwidth}
\begin{block}{Mikroszolgáltatások}
\begin{itemize}
\item Sok kis, önálló szolgáltatás, saját adatbázissal
\item Külön-külön skálázhatók és deployolhatók
\item Nagyobb komplexitás: hálózati kommunikáció, elosztott tranzakciók
\end{itemize}
\end{block}
\end{column}
\end{columns}
\begin{alertblock}{Gyakorlati tanács}
Kezdj monolittal! A tárgy során épített API is monolit lesz -- a mikroszolgáltatásokra való szétbontás egy már működő, jól rétegzett monolitból sokkal könnyebb, mint elölről tervezni.
\end{alertblock}
\end{frame}
\begin{frame}{Separation of Concerns}
\footnotesize
A "Separation of Concerns" (felelősségek szétválasztása) az az elv, ami az összes eddigi mintát (rétegzés, MVC) megalapozza: minden kódegységnek egyetlen, jól körülhatárolt felelőssége legyen.
\begin{itemize}
\item Egy függvény/osztály ne csinálja egyszerre a HTTP kérés kezelését \emph{és} az adatbázis-lekérdezést \emph{és} az email küldést
\item Ha egy dolog megváltozik (pl. az adatbázis lekérdezésének módja), az csak egy helyen igényeljen módosítást
\item Könnyebb egyesével, elszigetelten tesztelni az egyes felelősségeket
\end{itemize}
\begin{block}{Ökölszabály}
Ha egy függvény nevét csak "és"-sel tudod leírni (pl. "validál ÉS ment ÉS emailt küld"), valószínűleg érdemes szétbontani.
\end{block}
\end{frame}
\begin{frame}{Kitekintés -- mi jön még ebben a témában}
\footnotesize
A rétegzett architektúra és a Separation of Concerns elve két fogalmat is megalapoz, amikkel a félév további részében részletesen foglalkozunk majd -- most csak annyit érdemes megjegyezni, hogy hova illeszkednek.
\begin{itemize}
\item \textbf{Repositories és Aggregates (5. hét):} az Infrastructure réteg formalizált, ORM-alapú megvalósítása -- a repository "elrejti" az adatbázis-lekérdezéseket egy egyszerű interfész mögé
\item \textbf{Dependency Injection (11. hét):} technika arra, hogy a rétegek közötti függéseket (pl. "a controllernek szüksége van a service-re") ne kézzel drótozzuk be, hanem egy keretrendszer intézze
\end{itemize}
\end{frame}
\begin{frame}{Összefoglalás -- Architekturális felépítések}
\footnotesize
\begin{block}{Architektúra}
\begin{itemize}
\item A jó architektúra karbantarthatóvá, tesztelhetővé és bővíthetővé teszi a rendszert
\item Rétegzett architektúra (öt réteg): API/Controller -- Application -- Domain -- Communication -- Infrastructure
\item MVC: Model-View-Controller -- a Controller felel meg a jövő heti Express.js route handlernek
\item Monolit vs mikroszolgáltatás: kezdj monolittal, bontsd szét később, ha indokolt
\item Separation of Concerns: minden kódegységnek egy jól körülhatárolt felelőssége legyen
\end{itemize}
\end{block}
\end{frame}
@@ -0,0 +1,125 @@
\section{Routing}
\begin{frame}[fragile]{HTTP metódusok Express-ben}
\footnotesize
Minden HTTP metódushoz külön Express függvény tartozik, ami pontosan a metódus nevét viseli -- ez sokkal olvashatóbb, mint egy \texttt{if (req.method === ...)} lánc.
\begin{lstlisting}[language=JavaScript,style=NodeJS]
app.get('/users', (req, res) => { /* lista */ });
app.post('/users', (req, res) => { /* letrehozas */ });
app.get('/users/:id', (req, res) => { /* egy elem */ });
app.put('/users/:id', (req, res) => { /* teljes frissites */ });
app.patch('/users/:id', (req, res) => { /* reszleges frissites */ });
app.delete('/users/:id', (req, res) => { /* torles */ });
\end{lstlisting}
\begin{block}{Emlékeztető}
Ez pontosan a 2. héten (a jövő héten, amikor visszatérünk rá) tárgyalt CRUD $\to$ HTTP metódus táblázatot követi -- nem véletlen az egyezés.
\end{block}
\end{frame}
\begin{frame}[fragile]{Route paraméterek és query stringek}
\footnotesize
Az útvonalban lévő \texttt{:id}-hez hasonló szegmenseket \textbf{route paramétereknek} hívjuk, a kérdőjel utáni \texttt{?key=value} részt pedig \textbf{query stringnek}. Express mindkettőt készen, feldolgozva adja át.
\begin{lstlisting}[language=JavaScript,style=NodeJS]
// GET /users/123?includeOrders=true
app.get('/users/:id', (req, res) => {
const userId = req.params.id; // "123"
const includeOrders = req.query.includeOrders; // "true"
res.json({ id: userId, includeOrders });
});
\end{lstlisting}
\begin{itemize}
\item \textbf{\texttt{req.params}:} az útvonal része, egy konkrét erőforrás azonosítására (\texttt{/users/:id})
\item \textbf{\texttt{req.query}:} opcionális finomítás (lapozás, szűrés, rendezés)
\end{itemize}
\end{frame}
\begin{frame}[fragile]{A kérés törzse: \texttt{req.body}}
\footnotesize
\texttt{POST}, \texttt{PUT} és \texttt{PATCH} kéréseknél az adat jellemzően a kérés törzsében (body) érkezik JSON formátumban -- ehhez szükséges az előző órán megismert \texttt{express.json()} middleware.
\begin{lstlisting}[language=JavaScript,style=NodeJS]
app.use(express.json());
app.post('/users', (req, res) => {
const { name, email } = req.body;
const newUser = { id: Date.now(), name, email };
users.push(newUser);
res.status(201).json(newUser);
});
\end{lstlisting}
\begin{block}{Emlékeztető az 1. hétről}
A \texttt{201 Created} a helyes státuszkód sikeres \texttt{POST} esetén -- ezt már az 1. héten megismertük.
\end{block}
\end{frame}
\begin{frame}[fragile]{Útvonalak szervezése: \texttt{express.Router()}}
\footnotesize
Ahogy nő az alkalmazás, egyetlen fájlban tartani az összes route-ot áttekinthetetlenné válik. Az \texttt{express.Router()} lehetővé teszi, hogy erőforrásonként külön fájlba szervezzük az útvonalakat.
\begin{columns}
\begin{column}{0.48\textwidth}
\begin{block}{routes/users.js}
\begin{lstlisting}[language=JavaScript,style=NodeJS]
const router = express.Router();
router.get('/', listUsers);
router.post('/', createUser);
module.exports = router;
\end{lstlisting}
\end{block}
\end{column}
\begin{column}{0.48\textwidth}
\begin{block}{app.js}
\begin{lstlisting}[language=JavaScript,style=NodeJS]
const usersRouter =
require('./routes/users');
app.use(
'/users',
usersRouter
);
\end{lstlisting}
\end{block}
\end{column}
\end{columns}
\end{frame}
\begin{frame}[fragile]{Hibakezelés alapjai}
\footnotesize
Express két speciális esetet kezel middleware-ekkel: amikor egy útvonal nem létezik, és amikor egy route handler hibát dob.
\begin{lstlisting}[language=JavaScript,style=NodeJS]
// 404 - ha semelyik route nem illeszkedett
app.use((req, res) => {
res.status(404).json({ error: 'Nem talalhato' });
});
// hibakezelo middleware -- negy parameter jelzi Expressnek!
app.use((err, req, res, next) => {
console.error(err);
res.status(500).json({ error: 'Szerver hiba' });
});
\end{lstlisting}
\begin{block}{Fontos}
A hibakezelő middleware-t \emph{a legvégén} kell regisztrálni, az összes route után -- Express a négy paraméter alapján ismeri fel.
\end{block}
\end{frame}
\begin{frame}{Összefoglalás -- Routing}
\footnotesize
\begin{block}{Routing Express-ben}
\begin{itemize}
\item \texttt{app.get/post/put/patch/delete} -- metódusonként külön, olvasható route definíció
\item \texttt{req.params} -- útvonal paraméterek, \texttt{req.query} -- query string, \texttt{req.body} -- kérés törzse
\item \texttt{express.Router()} -- útvonalak szervezése külön fájlokba, erőforrásonként
\item 404 és hibakezelő middleware a route-ok végén
\end{itemize}
\end{block}
\end{frame}
-115
View File
@@ -1,115 +0,0 @@
\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}
@@ -0,0 +1,67 @@
\section{Postman}
\begin{frame}{Mi a Postman, és miért használjuk?}
\footnotesize
Eddig minden végpontunkat vagy böngészőből (csak GET-hez alkalmas), vagy egyáltalán nem próbáltuk ki. A \textbf{Postman} egy grafikus alkalmazás, amivel bármilyen HTTP kérést összeállíthatunk és elküldhetünk -- anélkül, hogy előbb frontendet kellene írnunk hozzá.
\begin{itemize}
\item Bármilyen metódus (GET, POST, PUT, PATCH, DELETE) kipróbálható
\item Egyedi fejlécek (headers) és JSON törzs (body) is összeállítható
\item A választ (státuszkód, headerek, body) azonnal, formázva látjuk
\item Elmenthető, újra futtatható, csapattagokkal megosztható kéréssorozatok
\end{itemize}
\begin{block}{Miért nem elég a böngésző?}
A böngésző címsorába írt URL mindig \texttt{GET} kérést küld -- \texttt{POST}, \texttt{PUT}, \texttt{DELETE} kéréshez, vagy egyedi fejlécekhez (pl. \texttt{Authorization}) külön eszköz kell.
\end{block}
\end{frame}
\begin{frame}[fragile]{Egy kérés összeállítása}
\footnotesize
Egy Postman-kérés pontosan azokból az elemekből áll, amiket az 1. héten a HTTP kérés felépítéseként már megismertünk: metódus, URL, fejlécek, törzs.
\begin{block}{Amit beállítunk}
\begin{itemize}
\item \textbf{Metódus:} pl. \texttt{POST}
\item \textbf{URL:} \texttt{http://localhost:3000/users}
\item \textbf{Headers:} \texttt{Content-Type: application/json}
\item \textbf{Body (raw, JSON):}
\end{itemize}
\end{block}
\begin{verbatim}
{
"name": "Kis Anna",
"email": "anna@example.com"
}
\end{verbatim}
Küldés után azonnal látjuk a választ: \texttt{201 Created} és a visszakapott, azonosítóval kiegészített felhasználó.
\end{frame}
\begin{frame}{Collections és Environment változók}
\footnotesize
Ahogy egyre több végpontot tesztelünk, hasznos ezeket rendszerezni, és elkerülni, hogy mindig kézzel írjuk be az alap URL-t.
\begin{itemize}
\item \textbf{Collection:} kérések egy mappába rendezett gyűjteménye -- pl. minden "Users" végpont egy collection-ben
\item \textbf{Environment:} névhez kötött változók (pl. \texttt{baseUrl}), amikkel a kéréseinkben \texttt{\{\{baseUrl\}\}/users} írható a teljes URL helyett
\item Így egyetlen változó átírásával válthatunk fejlesztői és éles környezet között
\end{itemize}
\begin{block}{Gyakorlati tipp}
Fejlesztés közben minden újonnan megírt végpontot azonnal próbálj ki Postmanben -- ez sokkal gyorsabb visszajelzést ad, mint egy teljes frontend felület megvárása.
\end{block}
\end{frame}
\begin{frame}{Összefoglalás -- Postman}
\footnotesize
\begin{block}{Postman}
\begin{itemize}
\item Grafikus eszköz tetszőleges HTTP kérés összeállítására és elküldésére
\item Kiválthatja a böngészőt olyan esetekben, amikor nem elég a sima GET
\item Kérés = metódus + URL + headers + body -- ugyanaz, mint amit HTTP-ből már tudunk
\item Collections: kérések rendszerezése; Environments: újrafelhasználható változók
\end{itemize}
\end{block}
\end{frame}
@@ -1,17 +0,0 @@
\section{Következő lépések}
\begin{frame}{Következő lépések}
\footnotesize
Ma megismertük, hogyan tervezzünk jól használható REST API-t, hogyan szervezzük a backend kódunkat rétegekbe, és mikor érdemes szétválasztani az írási és olvasási műveleteket. A jövő héten ezekre a tervezési elvekre már konkrét kódot építünk.
\begin{enumerate}
\item \textbf{Express.js megismerése} -- a jövő heti keretrendszer, amivel az API/Controller réteget ténylegesen megírjuk
\item \textbf{Routing gyakorlása} -- a mai órán tervezett erőforrás-orientált URL-eket Express.js route handlerekként fogjuk implementálni
\item \textbf{Postman telepítése} -- API-k manuális teszteléséhez, hogy a saját végpontjainkat is ki tudjuk próbálni
\item \textbf{TypeScript alapok} -- a jövő héttől fokozatosan áttérünk típusos JavaScriptre
\end{enumerate}
\begin{alertblock}{Javaslat}
Nézd át még egyszer a CRUD $\to$ HTTP metódus táblázatot -- a jövő héten ezt fogjuk közvetlenül route definíciókra fordítani.
\end{alertblock}
\end{frame}
@@ -0,0 +1,75 @@
\section{TypeScript}
\begin{frame}{Miért TypeScript?}
\footnotesize
A JavaScript \textbf{gyengén típusos} nyelv: egy változó bármikor bármilyen típusú értéket felvehet, és a hibák jó része csak futás közben derül ki -- gyakran éppen egy éles felhasználónál. A \textbf{TypeScript} a JavaScript egy kiterjesztése, ami \emph{statikus típusokat} ad hozzá.
\begin{itemize}
\item A típushibák nagy része már fordításkor (kódírás közben) kiderül, nem futás közben
\item Az IDE sokkal pontosabb kódkiegészítést és hibajelzést tud adni
\item Nagyobb csapatban, nagyobb kódbázisban különösen megtérül
\item A TypeScript kód JavaScript-té fordul le -- a böngésző/Node.js sosem futtat TS-t közvetlenül
\end{itemize}
\begin{block}{Fontos}
A TypeScript \emph{opcionális} réteg a JavaScript fölött -- minden érvényes JavaScript kód (majdnem) érvényes TypeScript is, fokozatosan vezethető be egy meglévő projektbe.
\end{block}
\end{frame}
\begin{frame}[fragile]{Telepítés és futtatás}
\footnotesize
A TypeScript fájlok (\texttt{.ts} kiterjesztéssel) nem futtathatók közvetlenül Node.js-szel -- előbb JavaScript-té kell őket fordítani, vagy egy erre képes futtatót kell használnunk.
\begin{block}{Fordítás \texttt{tsc}-vel}
\begin{verbatim}
npm install -D typescript
npx tsc --init # letrehozza a tsconfig.json-t
npx tsc # lefordit minden .ts fajlt .js-re
\end{verbatim}
\end{block}
\begin{block}{Fejlesztés közben: \texttt{tsx}}
\begin{verbatim}
npx tsx index.ts
\end{verbatim}
A \texttt{tsx} fordítás nélkül, közvetlenül futtatja a TypeScript fájlt -- fejlesztés közben ez a kényelmesebb.
\end{block}
\end{frame}
\begin{frame}[fragile]{Alaptípusok és egy egyszerű interfész}
\footnotesize
A típusokat a változó/paraméter neve után, kettősponttal adjuk meg. Több mező közös leírásához pedig egy \texttt{interface}-t érdemes definiálni.
\begin{lstlisting}[language=JavaScript,style=NodeJS]
let name: string = 'Anna';
let age: number = 25;
let isActive: boolean = true;
let tags: string[] = ['admin', 'user'];
interface User {
id: number;
name: string;
email: string;
}
function greet(user: User): string {
return `Hello, ${user.name}!`;
}
\end{lstlisting}
\end{frame}
\begin{frame}{Összefoglalás -- TypeScript}
\footnotesize
\begin{block}{TypeScript alapok}
\begin{itemize}
\item A JavaScript statikus típusokkal kiegészített változata -- fordításkor szűri a típushibákat
\item \texttt{tsc} -- fordítás JavaScript-té; \texttt{tsx}/\texttt{ts-node} -- közvetlen futtatás fejlesztéskor
\item Alaptípusok: \texttt{string}, \texttt{number}, \texttt{boolean}, tömbök
\item \texttt{interface} -- több mezőből álló adatszerkezet típusának leírása
\end{itemize}
\end{block}
\begin{alertblock}{Több is van!}
Ez csak az alapok -- a generikus típusokról, a \texttt{union}/\texttt{intersection} típusokról és más haladó fogalmakról egy \emph{külön, önállóan is végigkövethető} kiegészítő anyagban olvashatsz, ha mélyebben el szeretnél merülni.
\end{alertblock}
\end{frame}
@@ -0,0 +1,16 @@
\section{Következő lépések}
\begin{frame}{Következő lépések}
\footnotesize
Ma megírtuk az első valódi Express.js alkalmazásunkat, megtanultuk, hogyan teszteljük Postmannel, és belekóstoltunk a TypeScriptbe. Eddig azonban inkább \emph{gyakorlati megérzés} alapján dolgoztunk -- a jövő héten ezt tudatos tervezéssé alakítjuk.
\begin{enumerate}
\item \textbf{REST API tervezési elvek} -- visszatekintünk a ma írt útvonalainkra: jól neveztük-e el őket?
\item \textbf{Architekturális felépítések} -- hogyan válasszuk szét a route handlert (amit ma írtunk) az üzleti logikától
\item \textbf{CQRS} -- mikor éri meg külön kezelni az írási és olvasási műveleteket
\end{enumerate}
\begin{alertblock}{Javaslat}
Nézd át még egyszer a ma írt route-jaidat -- a jövő héten pontosan ezekre a konkrét példákra fogunk tervezési elveket alkalmazni.
\end{alertblock}
\end{frame}