database added
This commit is contained in:
@@ -0,0 +1,354 @@
|
||||
\section{Aggregációk}
|
||||
|
||||
\begin{frame}{Mi az aggregáció?}
|
||||
\begin{block}{Aggregált lekérdezések}
|
||||
Adatok összesítése és elemzése számítások segítségével
|
||||
\end{block}
|
||||
|
||||
\begin{block}{Prisma aggregáció műveletek}
|
||||
\begin{itemize}
|
||||
\item \texttt{count} - Rekordok számlálása
|
||||
\item \texttt{avg} - Átlag számítás
|
||||
\item \texttt{sum} - Összegzés
|
||||
\item \texttt{min} - Minimum érték
|
||||
\item \texttt{max} - Maximum érték
|
||||
\item \texttt{groupBy} - Csoportosítás
|
||||
\end{itemize}
|
||||
\end{block}
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}[fragile]{Példa séma}
|
||||
\begin{block}{schema.prisma}
|
||||
\begin{lstlisting}[language=JavaScript]
|
||||
model Product {
|
||||
id Int @id @default(autoincrement())
|
||||
name String
|
||||
price Float
|
||||
stock Int
|
||||
categoryId Int
|
||||
category Category @relation(fields: [categoryId],
|
||||
references: [id])
|
||||
createdAt DateTime @default(now())
|
||||
}
|
||||
model Category {
|
||||
id Int @id @default(autoincrement())
|
||||
name String
|
||||
products Product[]
|
||||
}
|
||||
\end{lstlisting}
|
||||
\end{block}
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}[fragile]{Count - Számlálás}
|
||||
\begin{block}{Összes rekord számolása}
|
||||
\begin{lstlisting}[language=JavaScript]
|
||||
const prisma = require('./prisma');
|
||||
async function countProducts() {
|
||||
const count = await prisma.product.count();
|
||||
console.log(`Összes termék: ${count}`);
|
||||
}
|
||||
// Szűrt számlálás
|
||||
async function countExpensiveProducts() {
|
||||
const count = await prisma.product.count({
|
||||
where: {
|
||||
price: {
|
||||
gt: 1000
|
||||
}
|
||||
}
|
||||
});
|
||||
console.log(`Drága termékek: ${count}`);
|
||||
}
|
||||
\end{lstlisting}
|
||||
\end{block}
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}[fragile]{Aggregate - Alapok}
|
||||
\begin{block}{Egyszerű aggregáció}
|
||||
\begin{lstlisting}[language=JavaScript]
|
||||
async function getProductStats() {
|
||||
const stats = await prisma.product.aggregate({
|
||||
_count: true,
|
||||
_avg: {price: true},
|
||||
|
||||
_sum: {stock: true},
|
||||
|
||||
_min: {price: true},
|
||||
|
||||
_max: {price: true}
|
||||
});
|
||||
|
||||
console.log(stats);
|
||||
}
|
||||
\end{lstlisting}
|
||||
\end{block}
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}[fragile]{Aggregate - Eredmény}
|
||||
\begin{block}{Visszatérési érték példa}
|
||||
\begin{lstlisting}[language=JavaScript]
|
||||
{
|
||||
_count: 150,
|
||||
_avg: {
|
||||
price: 1250.50
|
||||
},
|
||||
_sum: {
|
||||
stock: 5420
|
||||
},
|
||||
_min: {
|
||||
price: 99.99
|
||||
},
|
||||
_max: {
|
||||
price: 9999.99
|
||||
}}
|
||||
\end{lstlisting}
|
||||
\end{block}
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}[fragile]{Aggregate - Szűréssel}
|
||||
\begin{block}{Where feltétellel}
|
||||
\begin{lstlisting}[language=JavaScript]
|
||||
async function getCategoryStats(categoryId) {
|
||||
const stats = await prisma.product.aggregate({
|
||||
where: {
|
||||
categoryId: categoryId
|
||||
},
|
||||
_count: true,
|
||||
_avg: {
|
||||
price: true
|
||||
},
|
||||
_sum: {
|
||||
stock: true
|
||||
}
|
||||
});
|
||||
return stats;
|
||||
}
|
||||
\end{lstlisting}
|
||||
\end{block}
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}[fragile]{GroupBy - Csoportosítás}
|
||||
\begin{block}{Kategóriánkénti csoportosítás}
|
||||
\begin{lstlisting}[language=JavaScript]
|
||||
async function groupByCategory() {
|
||||
const result = await prisma.product.groupBy({
|
||||
by: ['categoryId'],
|
||||
_count: true,
|
||||
_avg: {
|
||||
price: true
|
||||
},
|
||||
_sum: {
|
||||
stock: true
|
||||
}
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
\end{lstlisting}
|
||||
\end{block}
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}[fragile]{GroupBy - Eredmény}
|
||||
\begin{block}{Csoportosított adatok}
|
||||
\begin{lstlisting}[language=JavaScript]
|
||||
[
|
||||
{
|
||||
categoryId: 1,
|
||||
_count: 45,
|
||||
_avg: { price: 599.99 },
|
||||
_sum: { stock: 1200 }
|
||||
},
|
||||
{
|
||||
categoryId: 2,
|
||||
_count: 67,
|
||||
_avg: { price: 1299.50 },
|
||||
_sum: { stock: 890 }
|
||||
}
|
||||
]
|
||||
\end{lstlisting}
|
||||
\end{block}
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}[fragile]{GroupBy - Having feltétel}
|
||||
\begin{block}{Szűrés az aggregált eredményen}
|
||||
\begin{lstlisting}[language=JavaScript]
|
||||
async function getCategoriesWithHighAvgPrice() {
|
||||
const result = await prisma.product.groupBy({
|
||||
by: ['categoryId'],
|
||||
_avg: {price: true},
|
||||
_count: true,
|
||||
having: {
|
||||
price: {
|
||||
_avg: {gt: 1000}
|
||||
}
|
||||
}
|
||||
});
|
||||
return result;
|
||||
}
|
||||
\end{lstlisting}
|
||||
\end{block}
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}[fragile]{GroupBy - Rendezés}
|
||||
\begin{block}{OrderBy aggregált mezőn}
|
||||
\begin{lstlisting}[language=JavaScript]
|
||||
async function getCategoriesOrderedByAvgPrice() {
|
||||
const result = await prisma.product.groupBy({
|
||||
by: ['categoryId'],
|
||||
_avg: {price: true},
|
||||
_count: true,
|
||||
orderBy: {
|
||||
_avg: {price: 'desc'}
|
||||
}
|
||||
});
|
||||
return result;
|
||||
}
|
||||
\end{lstlisting}
|
||||
\end{block}
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}[fragile]{Több mező szerinti csoportosítás}
|
||||
\begin{block}{Összetett GroupBy}
|
||||
\begin{lstlisting}[language=JavaScript]
|
||||
// Bővített séma szükséges: inStock boolean mező
|
||||
async function groupByCategoryAndStock() {
|
||||
const result = await prisma.product.groupBy({
|
||||
by: ['categoryId', 'inStock'],
|
||||
_count: true,
|
||||
_avg: {price: true},
|
||||
orderBy: {categoryId: 'asc'}
|
||||
});
|
||||
return result;
|
||||
}
|
||||
\end{lstlisting}
|
||||
\end{block}
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}[fragile]{Relációk és aggregáció}
|
||||
\begin{block}{Kapcsolt táblák aggregálása}
|
||||
\begin{lstlisting}[language=JavaScript]
|
||||
async function getCategoriesWithProductCount() {
|
||||
const categories = await prisma.category.findMany({
|
||||
include: {
|
||||
_count: {
|
||||
select: {
|
||||
products: true
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
// Eredmény: [{ id: 1, name: 'Electronics',
|
||||
// _count: { products: 45 } }, ...]
|
||||
return categories;
|
||||
}
|
||||
\end{lstlisting}
|
||||
\end{block}
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}[fragile]{Relációs szűrés aggregációval}
|
||||
\begin{block}{Több mint N termékkel rendelkező kategóriák}
|
||||
\begin{lstlisting}[language=JavaScript]
|
||||
async function getCategoriesWithManyProducts() {
|
||||
const categories = await prisma.category.findMany({
|
||||
where: {
|
||||
products: {some: {}}
|
||||
},
|
||||
include: {
|
||||
_count: {
|
||||
select: {products: true}
|
||||
}
|
||||
}});
|
||||
// Csak azok a kategóriák, amikhez van termék
|
||||
return categories.filter(c => c._count.products > 10);
|
||||
}
|
||||
\end{lstlisting}
|
||||
\end{block}
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}[fragile]{Express API - Statisztika endpoint}
|
||||
\begin{block}{Aggregált adatok REST API-n keresztül}
|
||||
\begin{lstlisting}[language=JavaScript]
|
||||
const express = require('express');
|
||||
const prisma = require('./prisma');
|
||||
const app = express();
|
||||
|
||||
app.get('/api/stats/products', async (req, res) => {
|
||||
const stats = await prisma.product.aggregate({
|
||||
_count: true,
|
||||
_avg: { price: true },
|
||||
_sum: { stock: true },
|
||||
_min: { price: true },
|
||||
_max: { price: true }
|
||||
});
|
||||
res.json(stats);
|
||||
});
|
||||
\end{lstlisting}
|
||||
\end{block}
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}[fragile]{Express API - Csoportosított adatok}
|
||||
\begin{block}{GroupBy endpoint}
|
||||
\begin{lstlisting}[language=JavaScript]
|
||||
app.get('/api/stats/by-category', async (req, res) => {
|
||||
const stats = await prisma.product.groupBy({
|
||||
by: ['categoryId'],
|
||||
_count: true,
|
||||
_avg: { price: true },
|
||||
_sum: { stock: true },
|
||||
orderBy: {
|
||||
_avg: {
|
||||
price: 'desc'
|
||||
}
|
||||
}
|
||||
});
|
||||
res.json(stats);
|
||||
});
|
||||
\end{lstlisting}
|
||||
\end{block}
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}[fragile]{Egyedi aggregáció - Raw Query}
|
||||
\begin{block}{SQL használata komplex esetekben}
|
||||
\begin{lstlisting}[language=JavaScript]
|
||||
async function customAggregation() {
|
||||
const result = await prisma.$queryRaw`
|
||||
SELECT "categoryId",
|
||||
COUNT(*) as count,
|
||||
AVG(price) as avgPrice,
|
||||
SUM(stock) as totalStock
|
||||
FROM "Product"
|
||||
WHERE price > 100
|
||||
GROUP BY "categoryId"
|
||||
HAVING AVG(price) > 500
|
||||
ORDER BY avgPrice DESC
|
||||
`;
|
||||
return result;
|
||||
}
|
||||
\end{lstlisting}
|
||||
\end{block}
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}{Aggregáció - Best Practices}
|
||||
\begin{block}{Ajánlások}
|
||||
\begin{itemize}
|
||||
\item Használj \texttt{select} és \texttt{where} feltételeket a teljesítmény növelésére
|
||||
\item GroupBy esetén mindig gondold át az indexelést
|
||||
\item Nagy adathalmazoknál fontold meg a lapozást
|
||||
\item Komplex esetekben raw SQL lehet hatékonyabb
|
||||
\item Cacheld az aggregált eredményeket, ha ritkán változnak
|
||||
\item Használj \texttt{having} feltételt az aggregált adatok szűrésére
|
||||
\end{itemize}
|
||||
\end{block}
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}{Összefoglalás}
|
||||
\begin{block}{Prisma aggregációk}
|
||||
\begin{itemize}
|
||||
\item \textbf{count} - Egyszerű és szűrt számlálás
|
||||
\item \textbf{aggregate} - Átlag, összeg, min, max számítások
|
||||
\item \textbf{groupBy} - Csoportosítás és aggregálás
|
||||
\item \textbf{having} - Szűrés aggregált eredményen
|
||||
\item \textbf{orderBy} - Rendezés aggregált mezők szerint
|
||||
\item \textbf{\_count} - Kapcsolt rekordok számlálása
|
||||
\end{itemize}
|
||||
\end{block}
|
||||
\end{frame}
|
||||
@@ -0,0 +1,130 @@
|
||||
\section{CRUD műveletek}
|
||||
|
||||
\begin{frame}[fragile]{CRUD műveletek}
|
||||
\begin{block}{Mi az a CRUD?}
|
||||
\begin{itemize}
|
||||
\item \textbf{C}reate - Létrehozás
|
||||
\item \textbf{R}ead - Olvasás
|
||||
\item \textbf{U}pdate - Módosítás
|
||||
\item \textbf{D}elete - Törlés
|
||||
\end{itemize}
|
||||
\end{block}
|
||||
|
||||
\vspace{0.5cm}
|
||||
|
||||
\begin{block}{Adatbázis műveletek}
|
||||
Az alapvető adatbázis műveletek, amelyek minden adatkezelő rendszerben megtalálhatók.
|
||||
\end{block}
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}[fragile]{Create - Létrehozás}
|
||||
\begin{block}{SQL - INSERT}
|
||||
\begin{lstlisting}[language=SQL]
|
||||
INSERT INTO users (name, email, age)
|
||||
VALUES ('John Doe', 'john@example.com', 30);
|
||||
\end{lstlisting}
|
||||
\end{block}
|
||||
|
||||
\begin{block}{REST API - POST}
|
||||
\begin{lstlisting}[language=JavaScript]
|
||||
POST /api/users
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"name": "John Doe",
|
||||
"email": "john@example.com",
|
||||
"age": 30
|
||||
}
|
||||
\end{lstlisting}
|
||||
\end{block}
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}[fragile]{Read - Olvasás}
|
||||
\begin{block}{SQL - SELECT}
|
||||
\begin{lstlisting}[language=SQL]
|
||||
-- Összes rekord
|
||||
SELECT * FROM users;
|
||||
|
||||
-- Egy adott rekord
|
||||
SELECT * FROM users WHERE id = 1;
|
||||
|
||||
-- Szűrés és rendezés
|
||||
SELECT name, email FROM users
|
||||
WHERE age > 25
|
||||
ORDER BY name ASC;
|
||||
\end{lstlisting}
|
||||
\end{block}
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}[fragile]{Read - REST API}
|
||||
\begin{block}{REST API - GET}
|
||||
\begin{lstlisting}[language=JavaScript]
|
||||
// Összes felhasználó
|
||||
GET /api/users
|
||||
|
||||
// Egy adott felhasználó
|
||||
GET /api/users/1
|
||||
|
||||
// Szűrés query paraméterekkel
|
||||
GET /api/users?age=30&sort=name
|
||||
\end{lstlisting}
|
||||
\end{block}
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}[fragile]{Update - Módosítás}
|
||||
\begin{block}{SQL - UPDATE}
|
||||
\begin{lstlisting}[language=SQL]
|
||||
UPDATE users
|
||||
SET email = 'newemail@example.com', age = 31
|
||||
WHERE id = 1;
|
||||
\end{lstlisting}
|
||||
\end{block}
|
||||
|
||||
\begin{block}{REST API - PUT/PATCH}
|
||||
\begin{lstlisting}[language=JavaScript]
|
||||
// Teljes frissítés
|
||||
PUT /api/users/1
|
||||
{ "name": "John Doe", "email": "new@example.com", "age": 31 }
|
||||
|
||||
// Részleges frissítés
|
||||
PATCH /api/users/1
|
||||
{ "email": "new@example.com" }
|
||||
\end{lstlisting}
|
||||
\end{block}
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}[fragile]{Delete - Törlés}
|
||||
\begin{block}{SQL - DELETE}
|
||||
\begin{lstlisting}[language=SQL]
|
||||
-- Egy adott rekord törlése
|
||||
DELETE FROM users WHERE id = 1;
|
||||
|
||||
-- Minden rekord törlése (óvatosan!)
|
||||
DELETE FROM users;
|
||||
\end{lstlisting}
|
||||
\end{block}
|
||||
|
||||
\begin{block}{REST API - DELETE}
|
||||
\begin{lstlisting}[language=JavaScript]
|
||||
DELETE /api/users/1
|
||||
\end{lstlisting}
|
||||
\end{block}
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}{CRUD vs HTTP metódusok}
|
||||
\begin{table}
|
||||
\begin{tabular}{|l|l|l|l|}
|
||||
\hline
|
||||
\textbf{CRUD} & \textbf{HTTP} & \textbf{SQL} & \textbf{Leírás} \\
|
||||
\hline
|
||||
Create & POST & INSERT & Új erőforrás létrehozása \\
|
||||
\hline
|
||||
Read & GET & SELECT & Erőforrás lekérdezése \\
|
||||
\hline
|
||||
Update & PUT/PATCH & UPDATE & Erőforrás módosítása \\
|
||||
\hline
|
||||
Delete & DELETE & DELETE & Erőforrás törlése \\
|
||||
\hline
|
||||
\end{tabular}
|
||||
\end{table}
|
||||
\end{frame}
|
||||
Binary file not shown.
@@ -0,0 +1,26 @@
|
||||
\documentclass[usenames,dvipsnames,aspectratio=169]{beamer}
|
||||
\usepackage{../common/webfejl}
|
||||
|
||||
% Automatikus frame törés engedélyezése túl hosszú tartalomnál
|
||||
\setbeamertemplate{frametitle continuation}[from second][\insertcontinuationcountroman]
|
||||
|
||||
\title[Webtechnológia és webalkalmazás-fejlesztés - Rest API]{Webtechnológia és webalkalmazás-fejlesztés - Rest API}
|
||||
\subtitle{Rest API}
|
||||
|
||||
\begin{document}
|
||||
|
||||
\begin{frame}[plain]
|
||||
\titlepage
|
||||
\logoalul
|
||||
\end{frame}
|
||||
|
||||
\input{crud_operations.tex}
|
||||
\input{orm.tex}
|
||||
\input{prisma.tex}
|
||||
\input{database_connection.tex}
|
||||
\input{aggregates.tex}
|
||||
\input{repository.tex}
|
||||
\input{dto.tex}
|
||||
\input{mapper.tex}
|
||||
|
||||
\end{document}
|
||||
@@ -0,0 +1,66 @@
|
||||
\section{Adatbázis kapcsolat}
|
||||
|
||||
\begin{frame}{Kapcsolat alapok}
|
||||
\begin{block}{Mi az adatbázis kapcsolat?}
|
||||
\begin{itemize}
|
||||
\item A kliens és az adatbázis szerver közötti kommunikáció
|
||||
\item Hozzáférés a lekérdezésekhez és tranzakciókhoz
|
||||
\item Hitelesítés, jogosultság és titkosítás része a kapcsolatnak
|
||||
\end{itemize}
|
||||
\end{block}
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}{Kapcsolati paraméterek}
|
||||
\begin{itemize}
|
||||
\item Host, port, adatbázis név
|
||||
\item Felhasználónév, jelszó
|
||||
\item SSL/TLS beállítások
|
||||
\item Timeout és pool méretek
|
||||
\end{itemize}
|
||||
|
||||
\begin{exampleblock}{Connection string példa}
|
||||
\texttt{postgres://user:pass@localhost:5432/appdb}
|
||||
\end{exampleblock}
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}{Connection pooling}
|
||||
\begin{block}{Miért kell?}
|
||||
\begin{itemize}
|
||||
\item A kapcsolat fel- és leépítése drága
|
||||
\item Pool újrahasznosítja a kapcsolatokat
|
||||
\item Stabilabb teljesítmény csúcsterhelésen
|
||||
\end{itemize}
|
||||
\end{block}
|
||||
|
||||
\begin{alertblock}{Beállítási irányelv}
|
||||
Túl kicsi pool lassít, túl nagy pool túlterheli a szervert.
|
||||
\end{alertblock}
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}[fragile]{Konfiguráció alkalmazásban}
|
||||
\begin{block}{Környezeti változók}
|
||||
\begin{lstlisting}[language=bash]
|
||||
DATABASE_URL="postgres://user:pass@localhost:5432/appdb"
|
||||
\end{lstlisting}
|
||||
\end{block}
|
||||
|
||||
\begin{block}{Függőség kezelés}
|
||||
A kapcsolat beállításait ne kódold a forráskódba, használj env fájlt.
|
||||
\end{block}
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}{Prisma megjegyzés}
|
||||
\begin{itemize}
|
||||
\item Prisma-val a kapcsolat a \texttt{DATABASE\_URL}-on keresztül jön létre
|
||||
\item A koncepció ugyanaz, csak a keretrendszer kezeli a részleteket
|
||||
\item Ebben a részben nem feltétel a Prisma használata
|
||||
\end{itemize}
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}{Hibakezelés és stabilitás}
|
||||
\begin{itemize}
|
||||
\item Használj újracsatlakozást ideiglenes hibákra
|
||||
\item Naplózd a kapcsolat hibákat, de ne írj ki jelszavakat
|
||||
\item Tranzakcióknál kezeld a visszagörgetést
|
||||
\end{itemize}
|
||||
\end{frame}
|
||||
@@ -0,0 +1,486 @@
|
||||
\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}
|
||||
\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
|
||||
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 folytatás}
|
||||
\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);
|
||||
}
|
||||
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}
|
||||
\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 });
|
||||
}
|
||||
});
|
||||
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}
|
||||
\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
|
||||
});
|
||||
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 - Pure functions // dto/user.mapper.js}
|
||||
\begin{lstlisting}[language=JavaScript]
|
||||
function toUserResponseDTO(user) {
|
||||
return {
|
||||
id: user.id,
|
||||
email: user.email,
|
||||
name: user.name
|
||||
};
|
||||
}
|
||||
function toUserResponseDTOs(users) {
|
||||
return users.map(toUserResponseDTO);
|
||||
}
|
||||
module.exports = {
|
||||
toUserResponseDTO,
|
||||
toUserResponseDTOs
|
||||
};
|
||||
\end{lstlisting}
|
||||
\end{block}
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}[fragile]{Mapper használata}
|
||||
\begin{block}{Service-ben}
|
||||
\begin{lstlisting}[language=JavaScript]
|
||||
const { toUserResponseDTO, toUserResponseDTOs }
|
||||
= require('./dto/user.mapper');
|
||||
class UserService {
|
||||
async getAllUsers() {
|
||||
const users = await prisma.user.findMany();
|
||||
return toUserResponseDTOs(users);
|
||||
}
|
||||
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}
|
||||
@@ -0,0 +1,64 @@
|
||||
\section{Mapper}
|
||||
|
||||
\begin{frame}{Mapper minta szerepe}
|
||||
\begin{block}{Mi a mapper?}
|
||||
\begin{itemize}
|
||||
\item Átalakítás domain objektumok és DTO-k között
|
||||
\item Elválasztja az adatformátumot az üzleti modelltől
|
||||
\item Központi helyen tartja az átalakítás logikáját
|
||||
\end{itemize}
|
||||
\end{block}
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}{Miért hasznos?}
|
||||
\begin{itemize}
|
||||
\item Csökkenti a duplikált átalakításokat
|
||||
\item Stabil API-szerződések (DTO-k) a változó domain mellett
|
||||
\item Tesztelhető, moduláris és újrafelhasználható kód
|
||||
\end{itemize}
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}[fragile]{Egyszerű mapper példa}
|
||||
\begin{lstlisting}[language=JavaScript]
|
||||
// Domain objektum
|
||||
const user = {
|
||||
id: 1, name: "John Doe",
|
||||
email: "john@example.com", passwordHash: "..."
|
||||
};
|
||||
|
||||
// Mapper függvény
|
||||
const toUserDto = (u) => ({
|
||||
id: u.id, name: u.name, email: u.email
|
||||
});
|
||||
\end{lstlisting}
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}[fragile]{Kétirányú mapper}
|
||||
\begin{lstlisting}[language=JavaScript]
|
||||
const toUserEntity = (dto) => ({
|
||||
id: dto.id ?? undefined,
|
||||
name: dto.name,
|
||||
email: dto.email
|
||||
});
|
||||
\end{lstlisting}
|
||||
|
||||
\begin{block}{Megjegyzés}
|
||||
Írhatsz külön mappert bejövő és kimenő adatokra.
|
||||
\end{block}
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}{Mapper és repository}
|
||||
\begin{itemize}
|
||||
\item Repository a tárolást kezeli
|
||||
\item Mapper az objektumok formátumát kezeli
|
||||
\item Két külön felelősségi kör
|
||||
\end{itemize}
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}{Jó gyakorlatok}
|
||||
\begin{itemize}
|
||||
\item Ne másolj át érzékeny mezőket (pl. jelszó)
|
||||
\item Tartsd egy helyen az átalakításokat
|
||||
\item Használj tiszta függvényeket és kis helper-eket
|
||||
\end{itemize}
|
||||
\end{frame}
|
||||
@@ -0,0 +1,118 @@
|
||||
\section{ORM}
|
||||
|
||||
\begin{frame}{Mi az ORM?}
|
||||
\begin{block}{Object-Relational Mapping}
|
||||
\begin{itemize}
|
||||
\item Objektumok és relációs táblák közötti leképezés
|
||||
\item Osztályok \textrightarrow{} táblákat, objektumok \textrightarrow{} sorokat jelentenek
|
||||
\item Átfedő absztrakció az SQL felett
|
||||
\end{itemize}
|
||||
\end{block}
|
||||
|
||||
\begin{exampleblock}{Példa}
|
||||
\begin{itemize}
|
||||
\item \texttt{User} osztály \textrightarrow{} \texttt{users} tábla
|
||||
\item \texttt{user.email} mezők \textrightarrow{} \texttt{email} oszlop
|
||||
\end{itemize}
|
||||
\end{exampleblock}
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}{Miért használunk ORM-et?}
|
||||
\begin{itemize}
|
||||
\item Kevesebb boilerplate SQL, gyorsabb fejlesztés
|
||||
\item Típusbiztonság, IDE támogatás, egységes API
|
||||
\item Adatbázis-portabilitás (pl. PostgreSQL \textrightarrow{} MySQL)
|
||||
\item Biztonság: paraméterizált lekérdezések alapból
|
||||
\end{itemize}
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}{Korlátai és kockázatai}
|
||||
\begin{itemize}
|
||||
\item Teljesítmény: rosszul írt lekérdezések, N+1 probléma
|
||||
\item "Leaky abstraction": komplex SQL-t nehéz elrejteni
|
||||
\item Nem minden adatbázis funkció elérhető el könnyen
|
||||
\item Extra tanulási görbe és konfiguráció
|
||||
\end{itemize}
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}{Alap fogalmak}
|
||||
\begin{columns}
|
||||
\begin{column}{0.48\textwidth}
|
||||
\begin{block}{Entity}
|
||||
Egy tábla sorát leíró objektum.
|
||||
\end{block}
|
||||
|
||||
\begin{block}{Repository}
|
||||
Adathozzáférési réteget kapszuláz.
|
||||
\end{block}
|
||||
\end{column}
|
||||
\begin{column}{0.48\textwidth}
|
||||
\begin{block}{Unit of Work}
|
||||
Változások követése és egyben mentése.
|
||||
\end{block}
|
||||
|
||||
\begin{block}{Session/Context}
|
||||
Kapcsolat az adatbázissal, cache és tranzakciók.
|
||||
\end{block}
|
||||
\end{column}
|
||||
\end{columns}
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}{Kapcsolatok leképezése}
|
||||
\begin{itemize}
|
||||
\item One-to-One: \texttt{User} \textrightarrow{} \texttt{Profile}
|
||||
\item One-to-Many: \texttt{Author} \textrightarrow{} \texttt{Post[]}
|
||||
\item Many-to-Many: \texttt{Student} \textrightarrow{} \texttt{Course[]}
|
||||
\end{itemize}
|
||||
|
||||
\begin{block}{Felelősség}
|
||||
A helyes kulcsok, indexek és megszorítások ugyanúgy fontosak, mint ORM nélkül.
|
||||
\end{block}
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}[fragile]{SQL vs ORM lekérdezés}
|
||||
\begin{columns}
|
||||
\begin{column}{0.48\textwidth}
|
||||
\begin{block}{SQL}
|
||||
\begin{lstlisting}[language=SQL]
|
||||
SELECT id, name, email
|
||||
FROM users
|
||||
WHERE active = true
|
||||
ORDER BY name ASC;
|
||||
\end{lstlisting}
|
||||
\end{block}
|
||||
\end{column}
|
||||
\begin{column}{0.48\textwidth}
|
||||
\begin{block}{ORM (példa)}
|
||||
\begin{lstlisting}[language=Java]
|
||||
List<User> users = db.users()
|
||||
.where(u -> u.active == true)
|
||||
.orderBy(u -> u.name)
|
||||
.select("id", "name", "email")
|
||||
.toList();
|
||||
\end{lstlisting}
|
||||
\end{block}
|
||||
\end{column}
|
||||
\end{columns}
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}{Lazy vs Eager betöltés}
|
||||
\begin{block}{Lazy loading}
|
||||
Csak akkor tölt be kapcsolt adatokat, amikor elérjük őket.
|
||||
\end{block}
|
||||
\begin{block}{Eager loading}
|
||||
Előre betölt kapcsolatokat egy lekérdezéssel (JOIN).
|
||||
\end{block}
|
||||
\begin{alertblock}{N+1 probléma}
|
||||
Túl sok lekérdezés keletkezik, ha minden kapcsolt adat külön jön le.
|
||||
\end{alertblock}
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}{Jó gyakorlatok}
|
||||
\begin{itemize}
|
||||
\item Használj migrációkat a séma kezelésére
|
||||
\item Mérj teljesítményt és ne félj nyers SQL-t írni
|
||||
\item Állíts be indexeket a gyakori szűrésekhez
|
||||
\item Tranzakciókkal biztosítsd a konzisztenciát
|
||||
\end{itemize}
|
||||
\end{frame}
|
||||
@@ -0,0 +1,413 @@
|
||||
\section{Prisma ORM}
|
||||
|
||||
\begin{frame}{Mi az a Prisma?}
|
||||
\begin{block}{Prisma}
|
||||
Modern ORM (Object-Relational Mapping) Node.js és TypeScript környezethez
|
||||
\end{block}
|
||||
|
||||
\begin{block}{Főbb jellemzők}
|
||||
\begin{itemize}
|
||||
\item Típusbiztos adatbázis kliens
|
||||
\item Deklaratív séma definíció
|
||||
\item Automatikus migráció kezelés
|
||||
\item Intuitív API CRUD műveletekhez
|
||||
\item Támogatott adatbázisok: PostgreSQL, MySQL, SQLite, MongoDB, stb.
|
||||
\end{itemize}
|
||||
\end{block}
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}[fragile]{Prisma telepítése}
|
||||
\begin{block}{NPM telepítés}
|
||||
\begin{lstlisting}[language=bash]
|
||||
# Prisma CLI telepítése dev dependencyként
|
||||
npm install prisma --save-dev
|
||||
|
||||
# Prisma Client telepítése
|
||||
npm install @prisma/client
|
||||
|
||||
# Prisma inicializálása
|
||||
npx prisma init
|
||||
\end{lstlisting}
|
||||
\end{block}
|
||||
|
||||
\begin{block}{Létrejövő fájlok}
|
||||
\begin{itemize}
|
||||
\item \texttt{prisma/schema.prisma} - Adatbázis séma
|
||||
\item \texttt{.env} - Környezeti változók (pl. DATABASE\_URL)
|
||||
\end{itemize}
|
||||
\end{block}
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}[fragile]{Prisma Schema alapok}
|
||||
\begin{block}{schema.prisma}
|
||||
\begin{lstlisting}[language=JavaScript]
|
||||
generator client {
|
||||
provider = "prisma-client-js"
|
||||
}
|
||||
datasource db {
|
||||
provider = "postgresql"
|
||||
url = env("DATABASE_URL")
|
||||
}
|
||||
model User {
|
||||
id Int @id @default(autoincrement())
|
||||
email String @unique
|
||||
name String?
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
}
|
||||
\end{lstlisting}
|
||||
\end{block}
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}[fragile]{Prisma migrációk}
|
||||
\begin{block}{Adatbázis migráció létrehozása és futtatása}
|
||||
\begin{lstlisting}[language=bash]
|
||||
# Migráció létrehozása
|
||||
npx prisma migrate dev --name init
|
||||
|
||||
# Prisma Client újragenerálása
|
||||
npx prisma generate
|
||||
|
||||
# Adatbázis vizualizáció
|
||||
npx prisma studio
|
||||
\end{lstlisting}
|
||||
\end{block}
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}[fragile]{Prisma Client inicializálása}
|
||||
\begin{block}{prisma.js}
|
||||
\begin{lstlisting}[language=JavaScript]
|
||||
const { PrismaClient } = require('@prisma/client');
|
||||
|
||||
const prisma = new PrismaClient();
|
||||
|
||||
module.exports = prisma;
|
||||
\end{lstlisting}
|
||||
\end{block}
|
||||
|
||||
\begin{alertblock}{Singleton pattern}
|
||||
Érdemes egyetlen Prisma Client példányt használni az alkalmazásban.
|
||||
\end{alertblock}
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}[fragile]{Create - Létrehozás}
|
||||
\begin{block}{Egy rekord létrehozása}
|
||||
\begin{lstlisting}[language=JavaScript]
|
||||
const prisma = require('./prisma');
|
||||
|
||||
async function createUser() {
|
||||
const user = await prisma.user.create({
|
||||
data: {
|
||||
email: 'john@example.com',
|
||||
name: 'John Doe'
|
||||
}
|
||||
});
|
||||
|
||||
console.log(user);
|
||||
// { id: 1, email: 'john@example.com', name: 'John Doe', ... }
|
||||
}
|
||||
\end{lstlisting}
|
||||
\end{block}
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}[fragile]{Create - Több rekord}
|
||||
\begin{block}{createMany használata}
|
||||
\begin{lstlisting}[language=JavaScript]
|
||||
async function createManyUsers() {
|
||||
const users = await prisma.user.createMany({
|
||||
data: [
|
||||
{ email: 'alice@example.com', name: 'Alice' },
|
||||
{ email: 'bob@example.com', name: 'Bob' },
|
||||
{ email: 'charlie@example.com', name: 'Charlie' }
|
||||
]
|
||||
});
|
||||
|
||||
console.log(`${users.count} felhasználó létrehozva`);
|
||||
}
|
||||
\end{lstlisting}
|
||||
\end{block}
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}[fragile]{Read - Összes rekord}
|
||||
\begin{block}{findMany használata}
|
||||
\begin{lstlisting}[language=JavaScript]
|
||||
async function getAllUsers() {
|
||||
const users = await prisma.user.findMany();
|
||||
console.log(users);
|
||||
}
|
||||
|
||||
// Rendezéssel
|
||||
async function getUsersSorted() {
|
||||
const users = await prisma.user.findMany({
|
||||
orderBy: {
|
||||
name: 'asc'
|
||||
}
|
||||
});
|
||||
}
|
||||
\end{lstlisting}
|
||||
\end{block}
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}[fragile]{Read - Egy rekord}
|
||||
\begin{block}{findUnique és findFirst}
|
||||
\begin{lstlisting}[language=JavaScript]
|
||||
// Egyedi mező alapján (pl. id, email)
|
||||
async function getUserById(id) {
|
||||
const user = await prisma.user.findUnique({
|
||||
where: { id: id }
|
||||
});
|
||||
return user;
|
||||
}
|
||||
// Első találat
|
||||
async function getUserByName(name) {
|
||||
const user = await prisma.user.findFirst({
|
||||
where: { name: name }
|
||||
});
|
||||
return user;
|
||||
}
|
||||
\end{lstlisting}
|
||||
\end{block}
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}[fragile]{Read - Szűrés}
|
||||
\begin{block}{Where feltételek}
|
||||
\begin{lstlisting}[language=JavaScript]
|
||||
async function filterUsers() {
|
||||
const users = await prisma.user.findMany({
|
||||
where: {
|
||||
email: {
|
||||
contains: '@example.com'
|
||||
},
|
||||
name: {
|
||||
startsWith: 'J'
|
||||
}
|
||||
}
|
||||
});
|
||||
return users;
|
||||
}
|
||||
\end{lstlisting}
|
||||
\end{block}
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}[fragile]{Read - Mezők kiválasztása}
|
||||
\begin{block}{Select használata}
|
||||
\begin{lstlisting}[language=JavaScript]
|
||||
async function getUserEmails() {
|
||||
const users = await prisma.user.findMany({
|
||||
select: {
|
||||
id: true,
|
||||
email: true,
|
||||
name: true
|
||||
}
|
||||
});
|
||||
return users;
|
||||
// [{ id: 1, email: '...', name: '...' }, ...]
|
||||
}
|
||||
\end{lstlisting}
|
||||
\end{block}
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}[fragile]{Update - Módosítás}
|
||||
\begin{block}{Egy rekord frissítése}
|
||||
\begin{lstlisting}[language=JavaScript]
|
||||
async function updateUser(id) {
|
||||
const user = await prisma.user.update({
|
||||
where: { id: id },
|
||||
data: {
|
||||
name: 'Jane Doe',
|
||||
email: 'jane@example.com'
|
||||
}
|
||||
});
|
||||
return user;
|
||||
}
|
||||
\end{lstlisting}
|
||||
\end{block}
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}[fragile]{Update - Több rekord}
|
||||
\begin{block}{updateMany használata}
|
||||
\begin{lstlisting}[language=JavaScript]
|
||||
async function updateManyUsers() {
|
||||
const result = await prisma.user.updateMany({
|
||||
where: {
|
||||
email: {
|
||||
contains: '@old-domain.com'
|
||||
}
|
||||
},
|
||||
data: {
|
||||
email: 'updated@new-domain.com'
|
||||
}
|
||||
});
|
||||
|
||||
console.log(`${result.count} rekord frissítve`);
|
||||
}
|
||||
\end{lstlisting}
|
||||
\end{block}
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}[fragile]{Delete - Törlés}
|
||||
\begin{block}{Egy rekord törlése}
|
||||
\begin{lstlisting}[language=JavaScript]
|
||||
async function deleteUser(id) {
|
||||
const user = await prisma.user.delete({
|
||||
where: { id: id }
|
||||
});
|
||||
return user;
|
||||
}
|
||||
\end{lstlisting}
|
||||
\end{block}
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}[fragile]{Delete - Több rekord}
|
||||
\begin{block}{deleteMany használata}
|
||||
\begin{lstlisting}[language=JavaScript]
|
||||
async function deleteInactiveUsers() {
|
||||
const result = await prisma.user.deleteMany({
|
||||
where: {
|
||||
createdAt: {
|
||||
lt: new Date('2020-01-01')
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
console.log(`${result.count} felhasználó törölve`);
|
||||
}
|
||||
\end{lstlisting}
|
||||
\end{block}
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}[fragile]{Relációk - Schema}
|
||||
\begin{block}{Egy-a-többhöz kapcsolat}
|
||||
\begin{lstlisting}[language=JavaScript]
|
||||
model User {
|
||||
id Int @id @default(autoincrement())
|
||||
name String
|
||||
posts Post[]
|
||||
}
|
||||
|
||||
model Post {
|
||||
id Int @id @default(autoincrement())
|
||||
title String
|
||||
content String?
|
||||
authorId Int
|
||||
author User @relation(fields: [authorId],
|
||||
references: [id])
|
||||
}
|
||||
\end{lstlisting}
|
||||
\end{block}
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}[fragile]{Relációk - Include}
|
||||
\begin{block}{Kapcsolt adatok lekérdezése}
|
||||
\begin{lstlisting}[language=JavaScript]
|
||||
async function getUserWithPosts(userId) {
|
||||
const user = await prisma.user.findUnique({
|
||||
where: { id: userId },
|
||||
include: {
|
||||
posts: true
|
||||
}
|
||||
});
|
||||
|
||||
console.log(user);
|
||||
// { id: 1, name: 'John', posts: [...] }
|
||||
}
|
||||
\end{lstlisting}
|
||||
\end{block}
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}[fragile]{Relációk - Nested Write}
|
||||
\begin{block}{Kapcsolt rekordok létrehozása}
|
||||
\begin{lstlisting}[language=JavaScript]
|
||||
async function createUserWithPosts() {
|
||||
const user = await prisma.user.create({
|
||||
data: {
|
||||
name: 'John Doe',
|
||||
email: 'john@example.com',
|
||||
posts: {
|
||||
create: [
|
||||
{ title: 'First Post', content: 'Hello!' },
|
||||
{ title: 'Second Post', content: 'World!' }
|
||||
]
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
\end{lstlisting}
|
||||
\end{block}
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}[fragile]{Tranzakciók}
|
||||
\begin{block}{Több művelet egy tranzakcióban}
|
||||
\begin{lstlisting}[language=JavaScript]
|
||||
async function transferPosts(fromUserId, toUserId) {
|
||||
const result = await prisma.$transaction([
|
||||
prisma.post.updateMany({
|
||||
where: { authorId: fromUserId },
|
||||
data: { authorId: toUserId }
|
||||
}),
|
||||
prisma.user.update({
|
||||
where: { id: fromUserId },
|
||||
data: { name: 'Archived User' }
|
||||
})
|
||||
]);
|
||||
|
||||
return result;
|
||||
}
|
||||
\end{lstlisting}
|
||||
\end{block}
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}[fragile]{Express integráció példa}
|
||||
\begin{block}{REST API endpoint Prisma-val}
|
||||
\begin{lstlisting}[language=JavaScript]
|
||||
const express = require('express');
|
||||
const prisma = require('./prisma');
|
||||
const app = express();
|
||||
app.use(express.json());
|
||||
app.get('/users', async (req, res) => {
|
||||
const users = await prisma.user.findMany();
|
||||
res.json(users);
|
||||
});
|
||||
app.post('/users', async (req, res) => {
|
||||
const user = await prisma.user.create({
|
||||
data: req.body
|
||||
});
|
||||
res.json(user);
|
||||
});
|
||||
\end{lstlisting}
|
||||
\end{block}
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}[fragile]{Hibakezelés}
|
||||
\begin{block}{Try-catch használata}
|
||||
\begin{lstlisting}[language=JavaScript]
|
||||
async function safeCreateUser(data) {
|
||||
try {
|
||||
const user = await prisma.user.create({
|
||||
data: data
|
||||
});
|
||||
return { success: true, user };
|
||||
} catch (error) {
|
||||
if (error.code === 'P2002') {
|
||||
return { success: false,
|
||||
error: 'Email már létezik' };
|
||||
}
|
||||
return { success: false, error: error.message };
|
||||
}
|
||||
}
|
||||
\end{lstlisting}
|
||||
\end{block}
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}{Prisma előnyei}
|
||||
\begin{block}{Miért használjuk?}
|
||||
\begin{itemize}
|
||||
\item \textbf{Típusbiztonság} - Auto-complete és típus ellenőrzés
|
||||
\item \textbf{Egyszerű API} - Intuitív, könnyen tanulható
|
||||
\item \textbf{Migráció kezelés} - Automatikus séma szinkronizáció
|
||||
\item \textbf{Teljesítmény} - Optimalizált query-k
|
||||
\item \textbf{Prisma Studio} - Vizuális adatbázis böngésző
|
||||
\item \textbf{Jó dokumentáció} - Részletes guide-ok és példák
|
||||
\end{itemize}
|
||||
\end{block}
|
||||
\end{frame}
|
||||
@@ -0,0 +1,79 @@
|
||||
\section{Repository}
|
||||
|
||||
\begin{frame}{Repository minta lényege}
|
||||
\begin{block}{Mire jó?}
|
||||
\begin{itemize}
|
||||
\item Elválasztja az adat-hozzáférést az üzleti logikától
|
||||
\item Egységes API az adatok kezelésére
|
||||
\item Könnyebben tesztelhető kód
|
||||
\end{itemize}
|
||||
\end{block}
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}{IRepository és konkret repository}
|
||||
\begin{block}{IRepository}
|
||||
\begin{itemize}
|
||||
\item Absztrakt interfész CRUD műveletekkel
|
||||
\item Pl. \texttt{findAll}, \texttt{findById}, \texttt{create}, \texttt{update}, \texttt{delete}
|
||||
\end{itemize}
|
||||
\end{block}
|
||||
|
||||
\begin{exampleblock}{Konkret repository}
|
||||
Az interfész implementációja (pl. \texttt{UserRepository}).
|
||||
\end{exampleblock}
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}[fragile]{IRepository példa (TypeScript)}
|
||||
\begin{lstlisting}[language=JavaScript]
|
||||
interface IRepository<T> {
|
||||
findAll(): Promise<T[]>;
|
||||
findById(id: number): Promise<T | null>;
|
||||
create(data: T): Promise<T>;
|
||||
update(id: number, data: Partial<T>): Promise<T>;
|
||||
delete(id: number): Promise<void>;
|
||||
}
|
||||
\end{lstlisting}
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}[fragile]{Prisma repository példa}
|
||||
\begin{block}{Prisma Client}
|
||||
A Prisma Client kezeli a kapcsolatot és az SQL generálást.
|
||||
\end{block}
|
||||
\begin{lstlisting}[language=JavaScript]
|
||||
class UserRepository implements IRepository<User> {
|
||||
constructor(private prisma: PrismaClient) {}
|
||||
findAll() {
|
||||
return this.prisma.user.findMany();
|
||||
}
|
||||
findById(id: number) {
|
||||
return this.prisma.user.findUnique({ where: { id } });
|
||||
}
|
||||
|
||||
|
||||
\end{lstlisting}
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}[fragile]{Prisma repository példa folytatás}
|
||||
\begin{lstlisting}[language=JavaScript]
|
||||
create(data: User) {
|
||||
return this.prisma.user.create({ data });
|
||||
}
|
||||
|
||||
update(id: number, data: Partial<User>) {
|
||||
return this.prisma.user.update({ where: { id }, data });
|
||||
}
|
||||
|
||||
async delete(id: number) {
|
||||
await this.prisma.user.delete({ where: { id } });
|
||||
}
|
||||
}
|
||||
\end{lstlisting}
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}{Mikor hasznos?}
|
||||
\begin{itemize}
|
||||
\item Ha több adatforrás van (SQL, API, cache)
|
||||
\item Ha szeretnél változtatható adat-hozzáférést
|
||||
\item Ha szükség van mock-olható IRepositories-re tesztekhez
|
||||
\end{itemize}
|
||||
\end{frame}
|
||||
Reference in New Issue
Block a user