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