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

Ответ

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 для «анемичных» сервисов: Если вся логика просто передаёт данные между слоями, это становится бесполезным абстракцией.

Ответ 18+ 🔞

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

Обычно этажи (слои) такие:

  1. Контроллеры (Presentation Layer): Эти ребята на входе. Их работа — встречать HTTP-запросы, как швейцар в отеле: проверить, что ты пришел в брюках, а не в трусах (валидация), позвать нужного человека внутри (сервис), и красиво подать ответ. Всё остальное — не их собачье дело.
  2. Сервисы (Business Logic Layer): А вот это уже мозги операции. Тут живут все правила, вся хитрая жопа приложения: «пользователь может купить только если у него есть бабки», «отправь письмо, но не чаще раза в день». Они не знают, откуда пришли данные и куда уйдут — им похуй.
  3. Репозитории (Data Access Layer): Подвальные гномы. Их удел — ковыряться в базе данных, лазить во внешние API, шуршать кэшем. Спросили — принесли. Сказали сохранить — сохранили. Никакой своей мысли, просто исполнители.

Выглядит это в папках примерно так:

src/
├── controllers/    # Встречают и провожают
│   └── userController.js
├── services/       # Мозги и правила
│   └── userService.js
├── repositories/   # Подвальные гномы-исполнители
│   └── userRepository.js
├── models/         # Описания, как данные выглядят
└── app.js

А теперь, ёпта, смотри как это в коде:

// repositories/userRepository.js (Гном в подвале)
const UserModel = require('../models/User');

class UserRepository {
  async findById(id) {
    return await UserModel.findById(id).lean(); // Просто тыркается в MongoDB
  }
  async create(userData) {
    const user = new UserModel(userData);
    return await user.save();
  }
}

module.exports = UserRepository;
// services/userService.js (Мозг с правилами)
const UserRepository = require('../repositories/userRepository');

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

  async registerUser(email, password) {
    // 1. Правило №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. Дополнительная фигня: отправить приветствие
    await this.sendWelcomeEmail(user.email);
    return user;
  }
}

module.exports = UserService;
// controllers/userController.js (Швейцар на входе)
const UserService = require('../services/userService');
const userService = new UserService();

const userController = {
  async createUser(req, res, next) {
    try {
      const { email, password } = req.body;
      // Валидация: а вдруг пришёл пустой запрос?
      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); // Если что-то пошло не так — кидаем ошибку дальше
    }
  }
};

module.exports = userController;

Чем это, блядь, хорошо в Node.js:

  • Тестировать просто: Можешь взять один сервис или репозиторий и тестировать его в полной изоляции, подсовывая моковые данные. Удобно, как свои тапки.
  • Менять технологии без боли: Захотел поменять MongoDB на PostgreSQL? Да похуй! Переписываешь только репозиторий, а вся бизнес-логика (сервисы) даже не чихнет. Доверия к такой системе — ебать ноль, но работает.
  • Читаемость: Открыл папку и сразу понял, где что искать. Не надо рыться в трёх тоннах спагетти-кода.

Но и минусы есть, ядрёна вошь:

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