Files
2026-02-24 01:29:59 +01:00

234 lines
6.0 KiB
TeX

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