234 lines
6.0 KiB
TeX
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}
|