Что такое Layered Architecture?
Комментарии (2)
Ответ сгенерирован нейросетью и может содержать ошибки
Layered Architecture: полный разбор
Layered Architecture — это один из самых популярных архитектурных паттернов в enterprise приложениях. Я использую его в большинстве проектов.
Основная структура
Приложение разделяется на слои, каждый с собственной ответственностью:
┌─────────────────────────────────┐
│ Presentation Layer │ ← REST API, WebSocket, Controllers
├─────────────────────────────────┤
│ Application / Business Logic │ ← Use Cases, Services
├─────────────────────────────────┤
│ Domain Layer │ ← Entities, Business Rules
├─────────────────────────────────┤
│ Infrastructure Layer │ ← Database, External APIs
└─────────────────────────────────┘
Зависимости идут ТОЛЬКО вниз ↓
Ключевое правило: внешние слои зависят от внутренних, но не наоборот.
Классический 3-слойный паттерн
В Node.js обычно используют:
1. Presentation Layer (Express/Fastify контроллеры)
// src/presentation/controllers/UserController.ts
import { CreateUserService } from '../../application/services/CreateUserService';
export class UserController {
constructor(private createUserService: CreateUserService) {}
async create(req: Request, res: Response) {
try {
// Валидация
const { email, password } = req.body;
// Вызываем бизнес-логику
const user = await this.createUserService.execute({
email,
password
});
// Форматируем ответ
res.status(201).json({
id: user.id,
email: user.email
});
} catch (error) {
res.status(400).json({ error: error.message });
}
}
}
2. Application Layer (Business Logic, Use Cases)
// src/application/services/CreateUserService.ts
import { UserRepository } from '../../infrastructure/repositories/UserRepository';
import { HashPassword } from '../../domain/services/HashPassword';
export class CreateUserService {
constructor(
private userRepository: UserRepository,
private hashPassword: HashPassword
) {}
async execute(input: { email: string; password: string }) {
// Бизнес-логика
const existingUser = await this.userRepository.findByEmail(input.email);
if (existingUser) {
throw new Error('User already exists');
}
const hashedPassword = await this.hashPassword.hash(input.password);
const user = new User({
email: input.email,
password: hashedPassword
});
return await this.userRepository.save(user);
}
}
3. Domain Layer (Entities, Business Rules)
// src/domain/entities/User.ts
export class User {
id: string;
email: string;
password: string;
createdAt: Date;
constructor(data: { email: string; password: string }) {
this.id = generateId();
this.email = email;
this.password = password;
this.createdAt = new Date();
}
// Бизнес-правила в самой сущности
isPasswordExpired(): boolean {
const daysSinceCreated =
(new Date().getTime() - this.createdAt.getTime()) / (1000 * 60 * 60 * 24);
return daysSinceCreated > 90;
}
updatePassword(newPassword: string): void {
if (newPassword.length < 6) {
throw new Error('Password must be at least 6 characters');
}
this.password = newPassword;
}
}
4. Infrastructure Layer (Database, External APIs)
// src/infrastructure/repositories/UserRepository.ts
import { User } from '../../domain/entities/User';
import { Database } from '../database/Database';
export class UserRepository {
constructor(private db: Database) {}
async findByEmail(email: string): Promise<User | null> {
const result = await this.db.query(
'SELECT * FROM users WHERE email = $1',
[email]
);
if (result.rows.length === 0) {
return null;
}
return this.mapToEntity(result.rows[0]);
}
async save(user: User): Promise<User> {
const result = await this.db.query(
'INSERT INTO users (id, email, password) VALUES ($1, $2, $3)',
[user.id, user.email, user.password]
);
return user;
}
private mapToEntity(row: any): User {
return new User({
email: row.email,
password: row.password
});
}
}
Структура проекта
src/
├── presentation/ ← HTTP Controllers, Request/Response
│ ├── controllers/
│ ├── routes/
│ └── middleware/
├── application/ ← Business Logic, Use Cases
│ ├── services/
│ └── dtos/
├── domain/ ← Entities, Interfaces, Business Rules
│ ├── entities/
│ ├── interfaces/
│ └── services/
└── infrastructure/ ← Database, External APIs, Libraries
├── repositories/
├── database/
└── external-services/
Зависимости между слоями
// ✅ ПРАВИЛЬНО
// Presentation → Application → Domain
UserController → CreateUserService → User
// ❌ НЕПРАВИЛЬНО
// Domain не должен знать о Presentation
User → UserController // Запрещено!
// ❌ НЕПРАВИЛЬНО
// Presentation не должен напрямую знать о Database
UserController → UserRepository // Плохо!
// Нужно через Application Layer
UserController → CreateUserService → UserRepository
Dependency Injection
Ключевой паттерн для layered architecture:
// src/application/services/CreateUserService.ts
export class CreateUserService {
constructor(
private userRepository: UserRepository, // Инъекция зависимостей
private hashPassword: HashPassword
) {}
// ...
}
// src/main.ts (bootstrapping)
const userRepository = new UserRepository(database);
const hashPassword = new HashPassword();
const createUserService = new CreateUserService(userRepository, hashPassword);
const userController = new UserController(createUserService);
Или с фреймворком (Nestjs):
// Nestjs автоматически управляет DI
import { Injectable } from '@nestjs/common';
@Injectable()
export class CreateUserService {
constructor(
@Inject(UserRepository) private userRepository: UserRepository,
private hashPassword: HashPassword
) {}
}
Преимущества Layered Architecture
✅ Разделение ответственности — каждый слой отвечает за своё ✅ Тестируемость — легко мокировать зависимости ✅ Масштабируемость — просто добавлять функционал ✅ Понятность — структура ясна новым разработчикам ✅ Переиспользование — логика не дублируется ✅ Замена реализации — поменять БД просто
Недостатки
❌ Complexity — оверкилл для маленьких проектов ❌ Boilerplate code — много классов и интерфейсов ❌ Performance — много слоёв = больше вызовов ❌ Learning curve — нужно понимать паттерны
Практический пример: Обновление юзера
// 1. Presentation
app.put('/api/users/:id', (req, res) => {
const updateUserService = container.get(UpdateUserService);
const user = await updateUserService.execute({
id: req.params.id,
email: req.body.email
});
res.json(user);
});
// 2. Application
class UpdateUserService {
async execute(input) {
const user = await this.userRepository.findById(input.id);
if (!user) throw new Error('User not found');
// Бизнес-логика
user.email = input.email;
return await this.userRepository.save(user);
}
}
// 3. Domain
class User {
email: string;
setEmail(newEmail: string) {
if (!this.isValidEmail(newEmail)) {
throw new Error('Invalid email');
}
this.email = newEmail;
}
}
// 4. Infrastructure
class UserRepository {
async save(user: User) {
return await db.query(
'UPDATE users SET email = $1 WHERE id = $2',
[user.email, user.id]
);
}
}
Layered Architecture — это фундамент для чистого, поддерживаемого кода в enterprise приложениях.