This commit is contained in:
magdo
2026-02-24 01:29:59 +01:00
parent 77f7eb2664
commit 31d4479c63
16 changed files with 4494 additions and 0 deletions
+671
View File
@@ -0,0 +1,671 @@
\section{Service Layer}
\begin{frame}[shrink=5]{Mi az a Service Layer?}
\begin{block}{Definíció}
A \textbf{Service Layer} (szolgáltatási réteg) egy tervezési minta, amely elválasztja az üzleti logikát a controller-ektől és a data access layer-től.
\end{block}
\begin{itemize}
\item \textbf{Separation of Concerns:} Felelősségek szétválasztása
\item \textbf{Reusability:} Újrafelhasználható üzleti logika
\item \textbf{Testability:} Könnyebb tesztelhetőség
\item \textbf{Maintainability:} Karbantarthatóság
\end{itemize}
\begin{exampleblock}{MVC architektúrában}
Controller → \textbf{Service Layer} → Repository/Model → Database
\end{exampleblock}
\end{frame}
\begin{frame}[shrink=5]{Háromrétegű architektúra}
\begin{columns}
\begin{column}{0.3\textwidth}
\begin{block}{Presentation Layer}
\begin{itemize}
\item Controllers
\item Routes
\item HTTP kérés/válasz
\item Validáció
\end{itemize}
\end{block}
\end{column}
\begin{column}{0.35\textwidth}
\begin{block}{Business Logic Layer}
\begin{itemize}
\item \textbf{Services}
\item Üzleti szabályok
\item Adatmanipuláció
\item Orchestration
\end{itemize}
\end{block}
\end{column}
\begin{column}{0.3\textwidth}
\begin{block}{Data Access Layer}
\begin{itemize}
\item Repository
\item Models
\item ORM
\item Database
\end{itemize}
\end{block}
\end{column}
\end{columns}
\begin{alertblock}{Fontos!}
A controller \textbf{NEM} tartalmaz üzleti logikát, csak delegál a service-eknek!
\end{alertblock}
\end{frame}
\begin{frame}[shrink=10]{Service Layer előnyei}
\begin{enumerate}
\item \textbf{Separation of Concerns}
\begin{itemize}
\item Tiszta felelősségi körök
\item Controller: HTTP kezelés
\item Service: Üzleti logika
\item Repository: Adatelérés
\end{itemize}
\item \textbf{Reusability}
\begin{itemize}
\item Több controller használhatja ugyanazt a service-t
\item Különböző kontextusokban (API, CLI, Background job)
\end{itemize}
\item \textbf{Testability}
\begin{itemize}
\item Service-ek könnyebben unit tesztelhetők
\item Mock-olható függőségek
\end{itemize}
\item \textbf{Maintainability}
\begin{itemize}
\item Változások izoláltak
\item Könnyebb hibakeresés
\end{itemize}
\end{enumerate}
\end{frame}
\begin{frame}[fragile,shrink=5]{AuthService - Authentikációs szolgáltatás}
\begin{block}{AuthService felelősségei}
\begin{itemize}
\item Felhasználó regisztráció
\item Bejelentkezés (login)
\item Token generálás és validálás
\item Jelszó hash-elés
\item Kijelentkezés (logout)
\end{itemize}
\end{block}
\begin{exampleblock}{AuthService példa}
\small
\begin{verbatim}
class AuthService {
async register(userData) { ... }
async login(email, password) { ... }
async logout(userId) { ... }
async refreshToken(refreshToken) { ... }
async verifyToken(token) { ... }
async resetPassword(email) { ... }
}
\end{verbatim}
\end{exampleblock}
\end{frame}
\begin{frame}[fragile,shrink=10]{AuthService implementáció - Register}
\begin{block}{Regisztráció üzleti logikája}
\small
\begin{verbatim}
class AuthService {
async register(userData) {
const existing = await this.userRepository
.findByEmail(userData.email);
if (existing) throw new Error('User exists');
const hashed = await bcrypt.hash(userData.password, 10);
const user = await this.userRepository.create({
...userData,
password: hashed
});
return { id: user.id, email: user.email };
}
}
\end{verbatim}
\end{block}
\end{frame}
\begin{frame}[fragile]{AuthService implementáció - Login}
\begin{block}{Bejelentkezés üzleti logikája}
\small
\begin{verbatim}
async login(email, password) {
const user = await this.userRepository.findByEmail(email);
if (!user) throw new Error('Invalid credentials');
const valid = await bcrypt.compare(password, user.password);
if (!valid) throw new Error('Invalid credentials');
const accessToken = jwt.sign(
{ userId: user.id, email: user.email },
process.env.JWT_SECRET, { expiresIn: '15m' }
);
const refreshToken = jwt.sign(
{ userId: user.id },
process.env.REFRESH_SECRET, { expiresIn: '7d' }
);
return { accessToken, refreshToken };
}
\end{verbatim}
\end{block}
\end{frame}
\begin{frame}[fragile]{Controller használja az AuthService-t}
\begin{block}{Thin Controller - Fat Service}
\small
\begin{verbatim}
app.post('/api/auth/register', async (req, res) => {
try {
const user = await authService.register(req.body);
res.status(201).json({ user });
} catch (error) {
res.status(400).json({ error: error.message });
}
});
app.post('/api/auth/login', async (req, res) => {
try {
const result = await authService.login(
req.body.email, req.body.password
);
res.cookie('refreshToken', result.refreshToken,
{ httpOnly: true, secure: true });
res.json({ accessToken: result.accessToken });
} catch (error) {
res.status(401).json({ error: error.message });
}
});
\end{verbatim}
\end{block}
\end{frame}
\begin{frame}[fragile]{UserService - Felhasználó kezelés}
\begin{block}{UserService felelősségei}
\begin{itemize}
\item Felhasználói profil lekérdezés
\item Profil módosítás
\item Jelszó változtatás
\item Felhasználó törlés
\item Felhasználó lista (admin)
\end{itemize}
\end{block}
\vspace{0.3cm}
\begin{exampleblock}{UserService példa}
\small
\begin{verbatim}
class UserService {
constructor(userRepository) {
this.userRepository = userRepository;
}
async getUserProfile(userId) { ... }
async updateProfile(userId, data) { ... }
async changePassword(userId, oldPassword, newPassword) { ... }
async deleteUser(userId) { ... }
async getAllUsers(filters) { ... }
}
\end{verbatim}
\end{exampleblock}
\end{frame}
\begin{frame}[fragile,shrink=5]{UserService implementáció}
\begin{block}{Jelszó változtatás üzleti logikával}
\small
\begin{verbatim}
class UserService {
async changePassword(userId, oldPass, newPass) {
const user = await this.userRepository.findById(userId);
if (!user) throw new Error('User not found');
const valid = await bcrypt.compare(oldPass, user.password);
if (!valid) throw new Error('Incorrect password');
const hashed = await bcrypt.hash(newPass, 10);
await this.userRepository.update(userId,
{ password: hashed });
await this.tokenService.revokeAllTokens(userId);
return { success: true };
}
}
\end{verbatim}
\end{block}
\end{frame}
\begin{frame}[fragile]{TokenService - Token kezelés}
\begin{block}{TokenService felelősségei}
\begin{itemize}
\item Access token generálás
\item Refresh token generálás
\item Token validálás
\item Token megújítás (refresh)
\item Token visszavonás (revoke)
\end{itemize}
\end{block}
\vspace{0.3cm}
\begin{exampleblock}{TokenService példa}
\small
\begin{verbatim}
class TokenService {
constructor(tokenRepository) {
this.tokenRepository = tokenRepository;
}
generateAccessToken(payload) { ... }
generateRefreshToken(userId) { ... }
async verifyAccessToken(token) { ... }
async refreshAccessToken(refreshToken) { ... }
async revokeToken(token) { ... }
async revokeAllTokens(userId) { ... }
}
\end{verbatim}
\end{exampleblock}
\end{frame}
\begin{frame}[fragile,shrink=5]{TokenService implementáció}
\begin{block}{Token refresh implementáció}
\small
\begin{verbatim}
class TokenService {
async refreshAccessToken(refreshToken) {
const decoded = jwt.verify(
refreshToken, process.env.REFRESH_SECRET);
const stored = await this.tokenRepository
.findByToken(refreshToken);
if (!stored || stored.revoked)
throw new Error('Token revoked');
const user = await this.userRepository
.findById(decoded.userId);
return {
accessToken: this.generateAccessToken(user)
};
}
}
\end{verbatim}
\end{block}
\end{frame}
\begin{frame}[fragile]{AuthorizationService - Autorizáció}
\begin{block}{AuthorizationService felelősségei}
\begin{itemize}
\item Jogosultság ellenőrzés
\item Role-based access control (RBAC)
\item Permission-based access control (PBAC)
\item Resource ownership ellenőrzés
\end{itemize}
\end{block}
\vspace{0.3cm}
\begin{exampleblock}{AuthorizationService példa}
\small
\begin{verbatim}
class AuthorizationService {
async hasRole(userId, role) { ... }
async hasPermission(userId, permission) { ... }
async canAccessResource(userId, resourceId, action) { ... }
async isOwner(userId, resourceId) { ... }
}
\end{verbatim}
\end{exampleblock}
\end{frame}
\begin{frame}[fragile]{AuthorizationService implementáció}
\begin{block}{Jogosultság ellenőrzés}
\small
\begin{verbatim}
class AuthorizationService {
constructor(userRepository, permissionRepository) {
this.userRepository = userRepository;
this.permissionRepository = permissionRepository;
}
async canAccessResource(userId, resourceId, action) {
const user = await this.userRepository.findById(userId);
// 1. Admin mindent csinálhat
if (user.role === 'admin') {
return true;
}
// 2. Ownership ellenőrzés
const resource = await this.resourceRepository.findById(resourceId);
if (resource.ownerId === userId && action === 'read') {
return true;
}
// 3. Permission alapú ellenőrzés
const permissions = await this.permissionRepository.getByUserId(userId);
return permissions.some(p => p.action === action && p.resource === resourceId);
}
}
\end{verbatim}
\end{block}
\end{frame}
\begin{frame}[fragile]{Dependency Injection}
\begin{block}{Service-ek kapcsolata}
A service-ek más service-eket használnak. Dependency Injection segít a függőségek kezelésében.
\end{block}
\vspace{0.3cm}
\begin{columns}
\begin{column}{0.48\textwidth}
\textbf{Rossz példa:}
\small
\begin{verbatim}
class AuthService {
constructor() {
this.userRepo =
new UserRepository();
}
}
\end{verbatim}
\end{column}
\begin{column}{0.48\textwidth}
\textbf{Jó példa (DI):}
\small
\begin{verbatim}
class AuthService {
constructor(userRepo) {
this.userRepo = userRepo;
}
}
const authService =
new AuthService(userRepo);
\end{verbatim}
\end{column}
\end{columns}
\end{frame}
\begin{frame}[fragile]{Service Container / DI Container}
\begin{block}{Automatikus Dependency Injection}
DI container-ek automatikusan kezelik a szolgáltatások létrehozását.
\end{block}
\vspace{0.3cm}
\begin{exampleblock}{Awilix library használata}
\small
\begin{verbatim}
const { createContainer, asClass } = require('awilix');
const container = createContainer();
container.register({
userRepository: asClass(UserRepository).singleton(),
tokenRepository: asClass(TokenRepository).singleton(),
authService: asClass(AuthService).singleton(),
userService: asClass(UserService).singleton(),
tokenService: asClass(TokenService).singleton()
});
const authService = container.resolve('authService');
\end{verbatim}
\end{exampleblock}
\end{frame}
\begin{frame}[fragile]{Error Handling a Service Layer-ben}
\begin{block}{Custom Error osztályok}
\small
\begin{verbatim}
class AuthenticationError extends Error {
constructor(message) {
super(message);
this.statusCode = 401;
}
}
class AuthorizationError extends Error {
constructor(message) {
super(message);
this.statusCode = 403;
}
}
class NotFoundError extends Error {
constructor(resource) {
super(`${resource} not found`);
this.statusCode = 404;
}
}
\end{verbatim}
\end{block}
\end{frame}
\begin{frame}[fragile]{Error Handling - Service használat}
\begin{block}{Service dobja a custom error-t}
\small
\begin{verbatim}
class AuthService {
async login(email, password) {
const user = await this.userRepository
.findByEmail(email);
if (!user) {
throw new AuthenticationError('Invalid credentials');
}
const valid = await bcrypt.compare(
password, user.password
);
if (!valid) {
throw new AuthenticationError('Invalid credentials');
}
// ...
}
}
\end{verbatim}
\end{block}
\vspace{0.2cm}
\begin{block}{Controller kezeli}
\small
\begin{verbatim}
app.post('/api/auth/login', async (req, res, next) => {
try {
const result = await authService.login(
req.body.email, req.body.password);
res.json(result);
} catch (error) {
next(error);
}
});
\end{verbatim}
\end{block}
\end{frame}
\begin{frame}[fragile]{Global Error Handler Middleware}
\begin{block}{Központi hibaüzenet kezelés}
\small
\begin{verbatim}
app.use((error, req, res, next) => {
console.error(error);
if (error.statusCode) {
return res.status(error.statusCode)
.json({ error: error.message });
}
res.status(500).json({
error: 'Internal Server Error'
});
});
\end{verbatim}
\end{block}
\end{frame}
\begin{frame}{Service Layer Testing}
\begin{block}{Unit Testing}
A service-ek izoláltan tesztelhetők mock repository-kkal és service-ekkel.
\end{block}
\vspace{0.3cm}
\begin{itemize}
\item \textbf{Előnyök:}
\begin{itemize}
\item Gyors tesztek (nincs adatbázis)
\item Üzleti logika fókusz
\item Mock-olható függőségek
\end{itemize}
\vspace{0.3cm}
\item \textbf{Test framework-ök:}
\begin{itemize}
\item Jest
\item Mocha + Chai
\item Vitest
\end{itemize}
\vspace{0.3cm}
\item \textbf{Mocking library-k:}
\begin{itemize}
\item Sinon.js
\item Jest built-in mocks
\end{itemize}
\end{itemize}
\end{frame}
\begin{frame}[fragile,shrink=5]{Service Unit Test példa}
\begin{block}{AuthService.login() teszt}
\small
\begin{verbatim}
describe('AuthService', () => {
let authService, mockUserRepo;
beforeEach(() => {
mockUserRepo = { findByEmail: jest.fn() };
authService = new AuthService(mockUserRepo);
});
test('login fails for invalid user', async () => {
mockUserRepo.findByEmail.mockResolvedValue(null);
await expect(
authService.login('test@test.com', 'pass')
).rejects.toThrow('Invalid credentials');
});
});
\end{verbatim}
\end{block}
\end{frame}
\begin{frame}{Service Layer Best Practices}
\begin{enumerate}
\item \textbf{Single Responsibility:} Egy service egy felelősségi kör
\item \textbf{Dependency Injection:} Konstruktorban injektált függőségek
\item \textbf{Thin Controllers:} Controller csak HTTP kezel
\item \textbf{Error Handling:} Custom error osztályok használata
\item \textbf{Async/Await:} Tiszta aszinkron kód
\item \textbf{Validation:} Input validáció a service-ben is
\item \textbf{Transaction Management:} Adatbázis tranzakciók
\item \textbf{Logging:} Strukturált log-olás
\end{enumerate}
\end{frame}
\begin{frame}{Projekt struktúra Service Layer-rel}
\begin{columns}
\begin{column}{0.48\textwidth}
\textbf{Fájl struktúra:}
\small
\begin{itemize}
\item \texttt{src/}
\begin{itemize}
\item \texttt{controllers/}
\begin{itemize}
\item \texttt{auth.controller.js}
\item \texttt{user.controller.js}
\end{itemize}
\item \texttt{services/}
\begin{itemize}
\item \texttt{auth.service.js}
\item \texttt{user.service.js}
\item \texttt{token.service.js}
\end{itemize}
\item \texttt{repositories/}
\begin{itemize}
\item \texttt{user.repository.js}
\item \texttt{token.repository.js}
\end{itemize}
\item \texttt{models/}
\item \texttt{middlewares/}
\item \texttt{utils/}
\end{itemize}
\end{itemize}
\end{column}
\begin{column}{0.48\textwidth}
\textbf{Rétegek felelősségei:}
\begin{itemize}
\item \textbf{Controller:}
\begin{itemize}
\item HTTP kérés/válasz
\item Validáció (input)
\item Service hívás
\end{itemize}
\vspace{0.3cm}
\item \textbf{Service:}
\begin{itemize}
\item Üzleti logika
\item Adatmanipuláció
\item Orchestration
\end{itemize}
\vspace{0.3cm}
\item \textbf{Repository:}
\begin{itemize}
\item Adatbázis műveletek
\item Query-k
\item ORM interaction
\end{itemize}
\end{itemize}
\end{column}
\end{columns}
\end{frame}
\begin{frame}{Összefoglalás}
\begin{itemize}
\item \textbf{Service Layer} = Üzleti logika réteg
\item \textbf{Separation of Concerns:} Controller, Service, Repository
\item \textbf{AuthService:} Regisztráció, login, token kezelés
\item \textbf{UserService:} Felhasználó kezelés, profil, jelszó
\item \textbf{TokenService:} Token generálás, validálás, refresh
\item \textbf{AuthorizationService:} Jogosultság ellenőrzés
\item \textbf{Dependency Injection:} Konstruktor alapú DI
\item \textbf{Error Handling:} Custom error osztályok
\item \textbf{Testing:} Unit teszt mock-okkal
\item \textbf{Best Practices:} Thin controller, fat service
\end{itemize}
\vspace{0.3cm}
\begin{exampleblock}{Miért használjuk?}
Tiszta kód, újrafelhasználhatóság, tesztelhetőség, karbantarthatóság
\end{exampleblock}
\end{frame}