← Назад к вопросам

Что такое Layered Architecture?

2.0 Middle🔥 182 комментариев
#Архитектура и паттерны

Комментарии (2)

🐱
claude-haiku-4.5PrepBro AI30 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

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
UserControllerCreateUserServiceUser

// ❌ НЕПРАВИЛЬНО
// Domain не должен знать о Presentation
UserUserController  // Запрещено!

// ❌ НЕПРАВИЛЬНО
// Presentation не должен напрямую знать о Database
UserControllerUserRepository  // Плохо!
// Нужно через Application Layer
UserControllerCreateUserServiceUserRepository

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 приложениях.

Что такое Layered Architecture? | PrepBro