Files
2026-02-17 21:17:42 +01:00

531 lines
14 KiB
TeX

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