% REST API architektúra \section{REST API Architektúra} \begin{frame}{Miért van szükség architektúrára?} \begin{block}{A probléma: "Spagetti kód"} Kezdő fejlesztők gyakran mindent egy helyre írnak: \begin{itemize} \item Adatbázis lekérdezések a route-okban \item Üzleti logika a controller-ekben \item Validáció szétszórva mindenhol \end{itemize} \textbf{Eredmény:} Áttekinthetetlen, nehezen karbantartható kód \end{block} \begin{exampleblock}{A megoldás: Rétegezett architektúra} Minden funkciónak megvan a saját helye és felelőssége. \end{exampleblock} \end{frame} \begin{frame}{5-rétegű architektúra} \begin{block}{Separation of Concerns - Felelősségek szétválasztása} \begin{itemize} \item \textbf{Karbantarthatóság} - Könnyebb módosítás, hibakeresés \item \textbf{Tesztelhetőség} - Rétegek külön tesztelhetők \item \textbf{Skálázhatóság (Scalability)} - Független fejlesztés és skálázás \item \textbf{Újrafelhasználhatóság} - Rétegek több helyen használhatók \end{itemize} \end{block} \begin{alertblock}{Alapelv} Minden réteg csak a saját felelősségével foglalkozik! \end{alertblock} \end{frame} \begin{frame}{Az 5 réteg} \begin{enumerate} \item \textbf{API Layer (Presentation Layer)} \begin{itemize} \item HTTP kérések fogadása \item Route-ok, Controller-ek \end{itemize} \item \textbf{Application Layer (Service Layer)} \begin{itemize} \item Üzleti logika koordinálása \item Service-ek \end{itemize} \item \textbf{Domain Layer (Business Logic)} \begin{itemize} \item Üzleti szabályok \item Model-ek, Entity-k \end{itemize} \end{enumerate} \end{frame} \begin{frame}{Az 5 réteg (folyt.)} \begin{enumerate} \setcounter{enumi}{3} \item \textbf{Infrastructure Layer} \begin{itemize} \item Adatbázis hozzáférés \item Repository-k, ORM \end{itemize} \item \textbf{Communication Layer} \begin{itemize} \item Adatok átalakítása \item DTO-k, Validáció \end{itemize} \end{enumerate} \end{frame} \begin{frame}[shrink=10]{Rétegek áttekintése - Diagram} \begin{center} \begin{tikzpicture}[node distance=0.3cm] \node[draw, rectangle, fill=blue!20, minimum width=9cm, minimum height=1cm, align=center] (api) {\textbf{API réteg (API Layer)}\\Útvonalak, Vezérlők (Routes, Controllers)}; \node[draw, rectangle, fill=green!20, minimum width=9cm, minimum height=1cm, below=of api, align=center] (app) {\textbf{Alkalmazás réteg (Application Layer)}\\Szolgáltatások (Services)}; \node[draw, rectangle, fill=yellow!20, minimum width=9cm, minimum height=1cm, below=of app, align=center] (domain) {\textbf{Tartomány réteg (Domain Layer)}\\Modellek, Entitások (Models, Entities)}; \node[draw, rectangle, fill=red!20, minimum width=9cm, minimum height=1cm, below=of domain, align=center] (infra) {\textbf{Infrastruktúra réteg (Infrastructure Layer)}\\Tárolók, Adatbázis (Repository, DB)}; \node[draw, rectangle, fill=purple!20, minimum width=9cm, minimum height=1cm, below=of infra, align=center] (comm) {\textbf{Kommunikációs réteg (Communication Layer)}\\Adatátviteli objektumok, Ellenőrzés (DTOs, Validation)}; \draw[->, thick] (api.south) -- (app.north); \draw[->, thick] (app.south) -- (domain.north); \draw[->, thick] (domain.south) -- (infra.north); \draw[<->, thick] (api.east) -- ++(1,0) |- (comm.east); \end{tikzpicture} \end{center} \end{frame} \begin{frame}[fragile,shrink=15]{API Layer - Route és Controller} \begin{exampleblock}{Route példa} \tiny \begin{verbatim} // routes/user.routes.js const express = require('express'); const router = express.Router(); const userController = require('../controllers/user.controller'); router.get('/', userController.getAllUsers); router.get('/:id', userController.getUserById); router.post('/', userController.createUser); module.exports = router; \end{verbatim} \end{exampleblock} \end{frame} \begin{frame}[fragile,shrink=15]{API Layer - Controller} \begin{exampleblock}{Controller példa} \tiny \begin{verbatim} // controllers/user.controller.js const userService = require('../services/user.service'); exports.getAllUsers = async (req, res) => { try { const users = await userService.getAllUsers(); res.json(users); } catch (error) { res.status(500).json({ error: error.message }); } }; exports.getUserById = async (req, res) => { try { const user = await userService.getUserById(req.params.id); if (!user) return res.status(404).json({ error: 'Not found' }); res.json(user); } catch (error) { res.status(500).json({ error: error.message }); } }; \end{verbatim} \end{exampleblock} \end{frame} \begin{frame}[fragile,shrink=15]{Application Layer - Service} \begin{exampleblock}{Service példa} \tiny \begin{verbatim} // services/user.service.js const userRepository = require('../repositories/user.repository'); exports.getAllUsers = async () => { return await userRepository.findAll(); }; exports.getUserById = async (id) => { return await userRepository.findById(id); }; exports.createUser = async (userData) => { // Üzleti logika validálás if (!userData.email || !userData.name) { throw new Error('Email and name are required'); } return await userRepository.create(userData); }; \end{verbatim} \end{exampleblock} \end{frame} \begin{frame}[fragile,shrink=15]{Domain Layer - Model/Entity} \begin{exampleblock}{User Model} \tiny \begin{verbatim} // models/user.model.js class User { constructor(id, name, email, createdAt) { this.id = id; this.name = name; this.email = email; this.createdAt = createdAt || new Date(); } // Üzleti logika metódusok isActive() { return this.active === true; } hasRole(role) { return this.roles.includes(role); } } module.exports = User; \end{verbatim} \end{exampleblock} \end{frame} \begin{frame}[fragile,shrink=15]{Infrastructure Layer - Repository} \begin{exampleblock}{Repository példa} \tiny \begin{verbatim} // repositories/user.repository.js const db = require('../config/database'); exports.findAll = async () => { return await db.query('SELECT * FROM users'); }; exports.findById = async (id) => { const result = await db.query('SELECT * FROM users WHERE id = ?', [id]); return result[0]; }; exports.create = async (userData) => { const result = await db.query( 'INSERT INTO users (name, email) VALUES (?, ?)', [userData.name, userData.email] ); return { id: result.insertId, ...userData }; }; \end{verbatim} \end{exampleblock} \end{frame} \begin{frame}[fragile,shrink=15]{Communication Layer - DTO} \begin{exampleblock}{Data Transfer Object} \tiny \begin{verbatim} // dtos/user.dto.js class UserDTO { constructor(user) { this.id = user.id; this.name = user.name; this.email = user.email; // Érzékeny mezők nem kerülnek át (pl. password) } } class CreateUserDTO { constructor(data) { this.name = data.name; this.email = data.email; this.password = data.password; } validate() { if (!this.email || !this.name || !this.password) { throw new Error('All fields required'); } } } \end{verbatim} \end{exampleblock} \end{frame} \begin{frame}{Projekt struktúra - Mappa felépítés} \begin{block}{Az 5-rétegű architektúra a mappákban} \small \begin{itemize} \item \textbf{\texttt{src/}} - Forráskód gyökér \item \quad \texttt{routes/} - \textcolor{blue}{API réteg}: útvonalak definiálása \item \quad \texttt{controllers/} - \textcolor{blue}{API réteg}: HTTP kérések kezelése \item \quad \texttt{services/} - \textcolor{green}{Alkalmazás réteg}: üzleti logika \item \quad \texttt{models/} - \textcolor{orange}{Tartomány réteg}: adatmodellek \item \quad \texttt{repositories/} - \textcolor{red}{Infrastruktúra réteg}: DB műveletek \item \quad \texttt{dtos/} - \textcolor{purple}{Kommunikáció réteg}: adatátviteli objektumok \item \quad \texttt{middlewares/} - Köztes réteg (autentikáció, naplózás) \item \quad \texttt{config/} - Konfigurációs fájlok \end{itemize} \end{block} \end{frame} \begin{frame}{Projekt struktúra - Függőségek} \begin{block}{Kommunikáció a rétegek között} \begin{itemize} \item \textbf{Routes} $\rightarrow$ Controllers (útvonal hívja a vezérlőt) \item \textbf{Controllers} $\rightarrow$ Services (vezérlő hívja a szolgáltatást) \item \textbf{Services} $\rightarrow$ Models + Repositories (szolgáltatás használja az adatréteget) \item \textbf{Repositories} $\rightarrow$ Database (tároló eléri az adatbázist) \item \textbf{DTOs} $\leftrightarrow$ Minden réteg (minden réteg használhat DTO-kat) \end{itemize} \end{block} \begin{alertblock}{Fontos szabály} A belső rétegek NEM függhetnek a külső rétegektől! A függőségek mindig befelé mutatnak. \end{alertblock} \end{frame} \begin{frame}{Clean Architecture} \begin{block}{Dependency Rule} Belső rétegek nem függhetnek külső rétegektől. A függőségek mindig befelé mutatnak. \end{block} \begin{exampleblock}{Előnyök} \begin{itemize} \item Független a framework-től \item Tesztelhető \item Független az UI-tól \item Független az adatbázistól \item Független külső szolgáltatásoktól \end{itemize} \end{exampleblock} \end{frame} \begin{frame}{Összefoglalás - Architektúra} \begin{itemize} \item 5-rétegű architektúra a clean code érdekében \item API Layer (API réteg): Route-ok és Controller-ek (útvonalak, vezérlők) \item Application Layer (Alkalmazás réteg): Service-ek (szolgáltatások - üzleti logika) \item Domain Layer (Tartomány réteg): Model-ek és Entity-k (modellek, entitások) \item Infrastructure Layer (Infrastruktúra réteg): Repository-k és DB (tárolók, adatbázis) \item Communication Layer (Kommunikációs réteg): DTO-k és validáció (adatátvitel, ellenőrzés) \end{itemize} \end{frame}