Что такое Layered Architecture (многослойная архитектура)?

«Что такое Layered Architecture (многослойная архитектура)?» — вопрос из категории Архитектура, который задают на 26% собеседований Node.js Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

Layered Architecture — это классический паттерн организации кода приложения, где ответственность разделена на горизонтальные слои. В Node.js-проектах это помогает поддерживать порядок, особенно в больших приложениях на Express.js или аналогичных фреймворках.

Типичные слои в Node.js-приложении:

  1. Слой представления/контроллеров (Presentation/Controller Layer): Обрабатывает HTTP-запросы и ответы. Его задача — валидация ввода, вызов бизнес-логики и форматирование вывода.
  2. Слой сервисов/бизнес-логики (Service/Business Logic Layer): Содержит ядро приложения — правила, алгоритмы, orchestration между разными сущностями.
  3. Слой доступа к данным (Data Access Layer / Repository): Инкапсулирует всю работу с источниками данных (базы данных, внешние API, кэш).

Пример структуры проекта Express.js:

src/
├── controllers/    # Presentation Layer
│   └── userController.js
├── services/       # Business Logic Layer
│   └── userService.js
├── repositories/   # Data Access Layer
│   └── userRepository.js
├── models/         # Data Models (например, Mongoose схемы)
└── app.js

Пример кода:

// repositories/userRepository.js (Data Access Layer)
const UserModel = require('../models/User');

class UserRepository {
  async findById(id) {
    return await UserModel.findById(id).lean(); // Используем Mongoose
  }
  async create(userData) {
    const user = new UserModel(userData);
    return await user.save();
  }
}

module.exports = UserRepository;
// services/userService.js (Business Logic Layer)
const UserRepository = require('../repositories/userRepository');

class UserService {
  constructor() {
    this.userRepo = new UserRepository();
  }

  async registerUser(email, password) {
    // 1. Проверка бизнес-правил
    if (!this.isValidEmail(email)) {
      throw new Error('Invalid email format');
    }
    // 2. Хеширование пароля (бизнес-логика безопасности)
    const hashedPassword = await bcrypt.hash(password, 10);
    // 3. Делегирование сохранения в репозиторий
    const user = await this.userRepo.create({ email, password: hashedPassword });
    // 4. Дополнительные действия (отправка welcome email и т.д.)
    await this.sendWelcomeEmail(user.email);
    return user;
  }
}

module.exports = UserService;
// controllers/userController.js (Presentation Layer)
const UserService = require('../services/userService');
const userService = new UserService();

const userController = {
  async createUser(req, res, next) {
    try {
      const { email, password } = req.body;
      // Валидация ввода (можно использовать Joi или express-validator)
      if (!email || !password) {
        return res.status(400).json({ error: 'Email and password are required' });
      }
      // Вызов бизнес-логики
      const newUser = await userService.registerUser(email, password);
      // Форматирование ответа
      res.status(201).json({
        message: 'User created',
        userId: newUser._id
      });
    } catch (error) {
      next(error); // Проброс ошибки в middleware обработки ошибок
    }
  }
};

module.exports = userController;

Преимущества в Node.js:

  • Тестируемость: Сервисы и репозитории можно легко тестировать изолированно с помощью моков (Jest, Sinon).
  • Замена технологий: Можно сменить базу данных (с MongoDB на PostgreSQL), переписав только репозиторий, не затрагивая бизнес-логику.
  • Читаемость: Код организован по функциональному признаку.

Недостатки:

  • Over-engineering для мелких проектов: Для простого CRUD API может создавать излишнюю сложность.
  • Potential для «анемичных» сервисов: Если вся логика просто передаёт данные между слоями, это становится бесполезным абстракцией.