\section{DTO} \begin{frame}{Mi az a DTO?} \begin{block}{Data Transfer Object} \begin{itemize} \item Adatok szállítására szolgáló objektum \item Elválasztja az adatbázis sémát\\ a kliens felőli interfésztől \item Kontroll az átadott adatok felett \item Validáció és transzformáció helye \end{itemize} \end{block} \begin{exampleblock}{Példa} Felhasználó entitás tartalmaz jelszó hash-t,\\ de a DTO nem adja ki. \end{exampleblock} \end{frame} \begin{frame}{Miért használjunk DTO-t?} \begin{block}{Előnyök} \begin{itemize} \item \textbf{Biztonság} - Érzékeny mezők elrejtése (pl. jelszó) \item \textbf{Verziókezelés} - API változhat a DB séma változtatása nélkül \item \textbf{Validáció} - Bejövő adatok ellenőrzése \item \textbf{Dokumentáció} - Világos szerződés a kliens és szerver között \item \textbf{Transzformáció} - Adatok átalakítása (pl. dátum formázás) \end{itemize} \end{block} \end{frame} \begin{frame}[fragile]{Prisma modell vs DTO} \begin{columns} \begin{column}{0.48\textwidth} \begin{block}{Prisma Model} \begin{lstlisting}[language=JavaScript] model User { id Int @id @default(autoincrement()) email String @unique password String name String? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt } \end{lstlisting} \end{block} \end{column} \begin{column}{0.48\textwidth} \begin{block}{UserDTO (kimenet)} \begin{lstlisting}[language=JavaScript] { id: 1, email: "john@example.com", name: "John Doe" // password nincs benne! // createdAt, updatedAt // opcionális } \end{lstlisting} \end{block} \end{column} \end{columns} \end{frame} \begin{frame}[fragile]{Input DTO - Létrehozás} \begin{block}{CreateUserDTO - Constructor} \begin{lstlisting}[language=JavaScript] class CreateUserDTO { constructor(data) { this.email = data.email; this.password = data.password; this.name = data.name; } \end{lstlisting} \end{block} \end{frame} \begin{frame}[fragile]{Input DTO - Validáció} \begin{block}{CreateUserDTO - Validate módszer} \begin{lstlisting}[language=JavaScript] class CreateUserDTO { // ... validate() { if (!this.email || !this.email.includes('@')) { throw new Error('Érvénytelen email cím'); } if (!this.password || this.password.length < 8) { throw new Error('Jelszó túl rövid (min. 8 karakter)'); } return true; } } \end{lstlisting} \end{block} \end{frame} \begin{frame}[fragile]{Output DTO - Válasz} \begin{block}{UserResponseDTO} \begin{lstlisting}[language=JavaScript] class UserResponseDTO { constructor(user) { this.id = user.id; this.email = user.email; this.name = user.name; // password nincs másolva! } static fromPrismaUser(user) { return new UserResponseDTO(user); } static fromManyPrismaUsers(users) { return users.map(u => new UserResponseDTO(u)); } } \end{lstlisting} \end{block} \end{frame} \begin{frame}[fragile]{DTO használata service-ben} \begin{block}{UserService példa (1)} \begin{lstlisting}[language=JavaScript] const prisma = require('./prisma'); const bcrypt = require('bcrypt'); class UserService { async createUser(createUserDTO) { createUserDTO.validate(); // Validálás const hashedPassword = await bcrypt.hash( createUserDTO.password, 10 ); // Jelszó hash-elés \end{lstlisting} \end{block} \end{frame} \begin{frame}[fragile]{DTO használata service-ben (folyt.)} \begin{block}{UserService példa (2)} \begin{lstlisting}[language=JavaScript] const user = await prisma.user.create({ data: { email: createUserDTO.email, password: hashedPassword, name: createUserDTO.name } // Mentés Prisma-val }); \end{lstlisting} \end{block} \end{frame} \begin{frame}[fragile]{DTO használata service-ben (folyt.)} \begin{block}{UserService példa (3)} \begin{lstlisting}[language=JavaScript] // DTO-vá alakítás visszaadás előtt return UserResponseDTO.fromPrismaUser(user); } async getAllUsers() { const users = await prisma.user.findMany(); return UserResponseDTO.fromManyPrismaUsers(users); } \end{lstlisting} \end{block} \end{frame} \begin{frame}[fragile]{DTO használata service-ben (folyt.)} \begin{block}{UserService példa (4)} \begin{lstlisting}[language=JavaScript] async getUserById(id) { const user = await prisma.user.findUnique({ where: { id } }); if (!user) return null; return UserResponseDTO.fromPrismaUser(user); } } \end{lstlisting} \end{block} \end{frame} \begin{frame}[fragile]{Express integráció DTO-val} \begin{block}{REST endpoint (POST)} \begin{lstlisting}[language=JavaScript] const express = require('express'); const userService = new UserService(); const app = express(); app.post('/api/users', async (req, res) => { try { const createDTO = new CreateUserDTO(req.body); const userDTO = await userService.createUser(createDTO); res.status(201).json(userDTO); } catch (error) { res.status(400).json({ error: error.message }); } }); \end{lstlisting} \end{block} \end{frame} \begin{frame}[fragile]{Express integráció DTO-val (folyt.)} \begin{block}{REST endpoint (GET)} \begin{lstlisting}[language=JavaScript] app.get('/api/users', async (req, res) => { const usersDTO = await userService.getAllUsers(); res.json(usersDTO); }); \end{lstlisting} \end{block} \end{frame} \begin{frame}[fragile]{Update DTO - Constructor} \begin{block}{UpdateUserDTO} \begin{lstlisting}[language=JavaScript] class UpdateUserDTO { constructor(data) { // Csak a megadott mezők kerülnek be if (data.email !== undefined) this.email = data.email; if (data.name !== undefined) this.name = data.name; if (data.password !== undefined) this.password = data.password; } \end{lstlisting} \end{block} \end{frame} \begin{frame}[fragile]{Update DTO - Validáció} \begin{block}{UpdateUserDTO - Validate} \begin{lstlisting}[language=JavaScript] class UpdateUserDTO { // ... validate() { if (this.email && !this.email.includes('@')) { throw new Error('Érvénytelen email'); } if (this.password && this.password.length < 8) { throw new Error('Jelszó túl rövid'); } return true; } } \end{lstlisting} \end{block} \end{frame} \begin{frame}[fragile]{Nested DTO - Relációkkal} \begin{block}{PostWithAuthorDTO} \begin{lstlisting}[language=JavaScript] class PostWithAuthorDTO { constructor(post) { this.id = post.id; this.title = post.title; this.content = post.content; this.createdAt = post.createdAt; // Nested DTO az author-hoz if (post.author) { this.author = new UserResponseDTO(post.author); } } static fromPrismaPost(post) { return new PostWithAuthorDTO(post); } } \end{lstlisting} \end{block} \end{frame} \begin{frame}[fragile]{Nested DTO használata} \begin{block}{Service metódus} \begin{lstlisting}[language=JavaScript] class PostService { async getPostWithAuthor(postId) { const post = await prisma.post.findUnique({ where: { id: postId }, include: { author: true } }); if (!post) return null; return PostWithAuthorDTO.fromPrismaPost(post); } } \end{lstlisting} \end{block} \end{frame} \begin{frame}[fragile]{Validációs könyvtár - Joi} \begin{block}{Joi telepítése} \begin{lstlisting}[language=bash] npm install joi \end{lstlisting} \end{block} \begin{block}{Validációs séma} \begin{lstlisting}[language=JavaScript] const Joi = require('joi'); const createUserSchema = Joi.object({ email: Joi.string().email().required(), password: Joi.string().min(8).required(), name: Joi.string().optional() }); \end{lstlisting} \end{block} \end{frame} \begin{frame}[fragile]{DTO Joi validációval} \begin{block}{CreateUserDTO sémával} \begin{lstlisting}[language=JavaScript] class CreateUserDTO { constructor(data) { const { error, value } = createUserSchema.validate(data); if (error) throw new Error(error.details[0].message); Object.assign(this, value); } } \end{lstlisting} \end{block} \end{frame} \begin{frame}[fragile]{Pagináció DTO} \begin{block}{PaginatedResponseDTO} \begin{lstlisting}[language=JavaScript] class PaginatedResponseDTO { constructor(data, total, page, pageSize) { this.data = data; this.pagination = { total, page, pageSize, totalPages: Math.ceil(total / pageSize) }; } } \end{lstlisting} \end{block} \end{frame} \begin{frame}[fragile]{Pagináció DTO használata} \begin{block}{PaginatedResponseDTOUsage} \begin{lstlisting}[language=JavaScript] // Használat const users = await prisma.user.findMany({ skip: (page - 1) * pageSize, take: pageSize }); const total = await prisma.user.count(); return new PaginatedResponseDTO( UserResponseDTO.fromManyPrismaUsers(users), total, page, pageSize ); \end{lstlisting} \end{block} \end{frame} \begin{frame}[fragile]{Szűrési DTO - Constructor} \begin{block}{UserFilterDTO} \begin{lstlisting}[language=JavaScript] class UserFilterDTO { constructor(query) { this.email = query.email; this.name = query.name; this.page = parseInt(query.page) || 1; this.pageSize = parseInt(query.pageSize) || 10; } \end{lstlisting} \end{block} \end{frame} \begin{frame}[fragile]{Szűrési DTO - Prisma konverzió} \begin{block}{toPrismaWhere módszer} \begin{lstlisting}[language=JavaScript] class UserFilterDTO { // ... toPrismaWhere() { const where = {}; if (this.email) where.email = { contains: this.email }; if (this.name) where.name = { contains: this.name }; return where; } } \end{lstlisting} \end{block} \end{frame} \begin{frame}[fragile]{Szűrési DTO használata} \begin{block}{Express endpoint szűréssel (1)} \begin{lstlisting}[language=JavaScript] app.get('/api/users', async (req, res) => { const filterDTO = new UserFilterDTO(req.query); const where = filterDTO.toPrismaWhere(); const users = await prisma.user.findMany({ where, skip: (filterDTO.page - 1) * filterDTO.pageSize, take: filterDTO.pageSize }); \end{lstlisting} \end{block} \end{frame} \begin{frame}[fragile]{Szűrési DTO használata (folyt.)} \begin{block}{Express endpoint szűréssel (2)} \begin{lstlisting}[language=JavaScript] const total = await prisma.user.count({ where }); const result = new PaginatedResponseDTO( UserResponseDTO.fromManyPrismaUsers(users), total, filterDTO.page, filterDTO.pageSize ); res.json(result); }); \end{lstlisting} \end{block} \end{frame} \begin{frame}[fragile]{Mapper függvények} \begin{block}{Alternatív megközelítés (1)} \begin{lstlisting}[language=JavaScript] function toUserResponseDTO(user) { return { id: user.id, email: user.email, name: user.name }; } function toUserResponseDTOs(users) { return users.map(toUserResponseDTO); } \end{lstlisting} \end{block} \end{frame} \begin{frame}[fragile]{Mapper függvények (folyt.)} \begin{block}{Alternatív megközelítés (2)} \begin{lstlisting}[language=JavaScript] module.exports = { toUserResponseDTO, toUserResponseDTOs }; \end{lstlisting} \end{block} \end{frame} \begin{frame}[fragile]{Mapper használata} \begin{block}{Service-ben (1)} \begin{lstlisting}[language=JavaScript] const { toUserResponseDTO, toUserResponseDTOs } = require('./dto/user.mapper'); class UserService { async getAllUsers() { const users = await prisma.user.findMany(); return toUserResponseDTOs(users); } \end{lstlisting} \end{block} \end{frame} \begin{frame}[fragile]{Mapper használata (folyt.)} \begin{block}{Service-ben (2)} \begin{lstlisting}[language=JavaScript] async getUserById(id) { const user = await prisma.user.findUnique({ where: { id } }); if (!user) return null; return toUserResponseDTO(user); } } \end{lstlisting} \end{block} \end{frame} \begin{frame}{DTO Best Practices} \begin{block}{Ajánlások} \begin{itemize} \item Külön DTO minden use case-hez (Create, Update, Response) \item Ne add vissza közvetlenül a Prisma modellt \item Validálj minden bejövő adatot \item Használj validációs könyvtárat (Joi, Yup, Zod) \item Tartsd egyszerűnek - ne túlbonyolítsd \item Dokumentáld a DTO mezőket \end{itemize} \end{block} \end{frame} \begin{frame}{DTO vs Prisma Select} \begin{columns} \begin{column}{0.48\textwidth} \begin{block}{Prisma Select} \begin{itemize} \item Egyszerű mezők kiválasztása \item Kevesebb kód \item Nincs validáció \item DB szintű szűrés \end{itemize} \end{block} \end{column} \begin{column}{0.48\textwidth} \begin{block}{DTO} \begin{itemize} \item Komplex transzformációk \item Validáció és logika \item Típusbiztonság \item API verziókezelés \end{itemize} \end{block} \end{column} \end{columns} \vspace{0.5cm} \begin{alertblock}{Kombináld őket!} Használd a Prisma select-et a DB szinten, és DTO-t a transzformációhoz. \end{alertblock} \end{frame} \begin{frame}{Összefoglalás} \begin{block}{DTO előnyei} \begin{itemize} \item \textbf{Biztonság} - Érzékeny adatok elrejtése \item \textbf{Validáció} - Adatok ellenőrzése \item \textbf{Transzformáció} - Adatok átalakítása \item \textbf{Dokumentáció} - Világos API szerződés \item \textbf{Karbantarthatóság} - Könnyebb változtatások \item \textbf{Tesztelhetőség} - Egyszerűbb unit tesztek \end{itemize} \end{block} \end{frame}