\section{Service Layer} \begin{frame}[shrink=15]{Mi az a Service Layer?} \begin{block}{Definíció} Tervezési minta - üzleti logika elválasztása controller-ektől és data layer-től. \end{block} \begin{itemize} \item Separation of Concerns \item Reusability (újrafelhasználható logika) \item Testability (könnyebb tesztelés) \item Maintainability \end{itemize} \begin{exampleblock}{Architektúra} Controller → Service → Repository → Database \end{exampleblock} \end{frame} \begin{frame}[shrink=15]{Háromrétegű architektúra} \begin{columns} \begin{column}{0.32\textwidth} \begin{block}{Presentation} \begin{small} Controllers, Routes\\ HTTP req/res\\ Validáció \end{small} \end{block} \end{column} \begin{column}{0.32\textwidth} \begin{block}{Business Logic} \begin{small} \textbf{Services}\\ Üzleti szabályok\\ Orchestration \end{small} \end{block} \end{column} \begin{column}{0.32\textwidth} \begin{block}{Data Access} \begin{small} Repository\\ ORM\\ Database \end{small} \end{block} \end{column} \end{columns} \begin{alertblock}{Fontos} Controller \textbf{NEM} tartalmaz üzleti logikát, csak delegál! \end{alertblock} \end{frame} \begin{frame}[shrink=15]{Service Layer előnyei} \begin{enumerate} \item \textbf{Separation}: Tiszta felelősségi körök \item \textbf{Reusability}: Több controller használhatja \item \textbf{Testability}: Könnyen unit tesztelhető \item \textbf{Maintainability}: Változások izoláltak \end{enumerate} \end{frame} \begin{frame}[fragile,shrink=20]{AuthService - Interface} \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{frame} \begin{frame}[fragile,shrink=20]{AuthService - Register} \begin{verbatim} class AuthService { async register(userData) { const exists = await this.userRepo.findByEmail(userData.email); if (exists) throw new Error('User exists'); const hashed = await bcrypt.hash(userData.password, 10); const user = await this.userRepo.create({ ...userData, password: hashed }); return { id: user.id, email: user.email }; } } \end{verbatim} \end{frame} \begin{frame}[fragile,shrink=20]{AuthService - Login} \begin{verbatim} async login(email, password) { const user = await this.userRepo.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 token = jwt.sign( { userId: user.id, role: user.role }, process.env.JWT_SECRET, { expiresIn: '1h' } ); return { token, user: { id: user.id, email: user.email } }; } \end{verbatim} \end{frame} \begin{frame}[fragile,shrink=20]{Controller használja Service-t} \begin{verbatim} class AuthController { constructor(authService) { this.authService = authService; } async register(req, res) { try { const user = await this.authService.register(req.body); res.status(201).json(user); } catch (err) { res.status(400).json({ error: err.message }); } } async login(req, res) { try { const result = await this.authService.login(req.body.email, req.body.password); res.json(result); } catch (err) { res.status(401).json({ error: err.message }); } } } \end{verbatim} \end{frame} \begin{frame}[fragile,shrink=20]{UserService példa} \begin{verbatim} class UserService { constructor(userRepo) { this.userRepo = userRepo; } async getById(id) { const user = await this.userRepo.findById(id); if (!user) throw new Error('User not found'); return user; } async update(id, data) { const user = await this.getById(id); return await this.userRepo.update(id, data); } async delete(id) { await this.getById(id); return await this.userRepo.delete(id); } } \end{verbatim} \end{frame} \begin{frame}[fragile,shrink=20]{Dependency Injection} \begin{verbatim} // Repository class UserRepository { async findById(id) { /* DB query */ } async create(data) { /* DB insert */ } } // Service class UserService { constructor(userRepository) { this.userRepo = userRepository; } // ... } // DI container const userRepo = new UserRepository(); const userService = new UserService(userRepo); const userController = new UserController(userService); \end{verbatim} \end{frame} \begin{frame}[shrink=15]{Service Layer Best Practices} \begin{enumerate} \item Service-ek ne dependáljanak controller-ektől \item Egy service = egy domain (User, Auth, Order) \item Dependency Injection használata \item Service-ek ne ismerjék HTTP-t (req, res) \item Hibakezelés service-ben (throw Error) \item Transaction logika service-ben \item Async/await következetes használata \end{enumerate} \end{frame} \begin{frame}[fragile,shrink=20]{Transaction példa Service-ben} \begin{verbatim} class OrderService { async createOrder(userId, items) { const transaction = await db.transaction(); try { const order = await this.orderRepo.create({ userId }, transaction); for (const item of items) { await this.orderItemRepo.create({ orderId: order.id, ...item }, transaction); } await transaction.commit(); return order; } catch (err) { await transaction.rollback(); throw err; } } } \end{verbatim} \end{frame} \begin{frame}[shrink=15]{Testing Service Layer} \begin{itemize} \item Service-ek unit tesztelése mock repository-val \item Ne kelljen DB a unit teszthez \item Integration teszt valódi DB-vel \end{itemize} \end{frame}