Layered Architecture
Layered Architecture (слоистая архитектура) — это паттерн организации кода Node.js/Express приложения, при котором код разделяется на чёткие слои: Routes → Controllers → Services → Repository/DAL — с однонаправленными зависимостями между ними.
Зачем нужно
Без структуры вся логика оседает в route-обработчиках: SQL-запросы, бизнес-правила, форматирование ответа. Это делает код нетестируемым и нерасширяемым. Слоистая архитектура разделяет ответственности: каждый слой занимается своим делом, легко тестируется в изоляции и может быть заменён независимо.
Где используется
- REST API на Express любого масштаба — от pet-проектов до enterprise
- Основа для чистой архитектуры и Hexagonal Architecture
- Шаблон для NestJS (контроллеры, провайдеры, модули)
- Стартовая точка для микросервисов
Основной контент
Слои и их ответственности
HTTP Client
↓
┌─────────────┐
│ Routes │ Объявление маршрутов, подключение middleware
└──────┬──────┘
↓
┌─────────────┐
│ Controllers │ Парсинг req, вызов Service, формирование res
└──────┬──────┘
↓
┌─────────────┐
│ Services │ Бизнес-логика, валидация, оркестрация
└──────┬──────┘
↓
┌─────────────┐
│ Repository │ Работа с БД: SQL/Mongoose-запросы
└─────────────┘
Структура файлов
src/
routes/
users.js ← маршруты
controllers/
UsersController.js ← обработка запроса/ответа
services/
UsersService.js ← бизнес-логика
repositories/
UsersRepository.js ← SQL/ORM запросы
models/
User.js ← схема БД (Mongoose/Prisma)
Repository — работа с БД
// repositories/UsersRepository.js
const { db } = require('../db');
class UsersRepository {
async findAll {
return db('users').select('*');
}
async findById(id) {
return db('users').where({ id }).first;
}
async create(data) {
const [id] = await db('users').insert(data);
return this.findById(id);
}
}
module.exports = new UsersRepository();
Service — бизнес-логика
// services/UsersService.js
const UsersRepository = require('../repositories/UsersRepository');
const { ValidationError, NotFoundError } = require('../errors');
const bcrypt = require('bcrypt');
class UsersService {
async create({ name, email, password }) {
const existing = await UsersRepository.findByEmail(email);
if (existing) throw new ValidationError('Email already in use');
const hashedPassword = await bcrypt.hash(password, 10);
return UsersRepository.create({ name, email, password: hashedPassword });
}
async getById(id) {
const user = await UsersRepository.findById(id);
if (!user) throw new NotFoundError('User');
return user;
}
}
module.exports = new UsersService();
Controller — обработка HTTP
// controllers/UsersController.js
const UsersService = require('../services/UsersService');
class UsersController {
async getAll(req, res, next) {
try {
const users = await UsersService.getAll;
res.json(users);
} catch (err) {
next(err);
}
}
async create(req, res, next) {
try {
const user = await UsersService.create(req.body);
res.status(201).json(user);
} catch (err) {
next(err);
}
}
}
module.exports = new UsersController();
Routes — объявление маршрутов
// routes/users.js
const express = require('express');
const router = express.Router;
const UsersController = require('../controllers/UsersController');
const { authMiddleware } = require('../middleware/auth');
router.get('/', authMiddleware, UsersController.getAll.bind(UsersController));
router.post('/', UsersController.create.bind(UsersController));
module.exports = router;
Частые ошибки
- Пропустить слой Service — писать SQL прямо в контроллере: бизнес-логика не тестируется изолированно
- Нарушать однонаправленность — Repository не должен знать о Controller или HTTP; Service не должен знать о req/res
- Один огромный сервис — разбивать по доменам (UsersService, OrdersService), а не один AppService
- Singleton-репозитории с состоянием — опасно при многопоточности (Worker Threads); лучше классы без состояния
Связанные темы
- _MOC Node.js
- REST API на Express
- Express -- Router и модуляризация
- ORM -- Prisma обзор
- Microservices -- обзор
Ресурсы
🎓 Источник: Слои, связанность и связность кода
- 📅 2018-10-23 · YouTube · [Marp](../../Documents/TimurShemsedinov/2018-10-23 — Слои, связанность и связность кода в JavaScript (A3RpwNlVeyY).md)
- Тезисы:
- Connascence: степень связи между компонентами; чем «ближе» — тем хуже масштабируется
- Слои движутся в одном направлении: api → domain → db (никогда обратно)
- High cohesion внутри слоя, low coupling между слоями — фундамент GRASP
- Конфиг — отдельный слой; не разбрасывай константы по бизнес-логике
🎓 Источник: Архитектурные принципы из курса Node.js 2024
- 📅 2023-12-06 · YouTube
- Тезисы: актуальная сборка архитектурных принципов: слои, DDD, DI, framework-agnostic подход
🎓 Источник: Node JS 2022-2023 практический курс
- 📅 2022-08-29 · YouTube
- Тезисы:
- Фреймворк-агностик подход — бизнес-логика не должна знать про Express/Nest
- Транспорт- и протокол-агностик: одна функция работает по HTTP, WebSocket, IPC
- DDD, SOLID, GRASP, паттерны GoF
- Шаблон приложения с готовой архитектурой
🎓 Источник: Модули, слои, структура проекта
- 📅 2018-10-02 · YouTube
- Тезисы:
- Подмодули в папке
lib, композиция черезObject.assignв цикле по именам - Логирование — отдельный модуль, композиция модулей в одном месте приложения
- ESM (
.mjs) и CommonJS несовместимы — не смешивать - Через
require.resolveиrequire.cacheможно перезагружать модули в рантайме
- Подмодули в папке
🎓 Источник: Структура приложения важнее архитектуры
- 📅 2026-05-16 · YouTube
- Тезисы:
- Альтернативная позиция: «Структура приложения важнее архитектуры». На архитектуру влияет не структурный код, а низкоуровневые характеристики: идемпотентность, состояние, ссылки.
- Структура приложения — это система модульности и приёмы её использования.
- Структура данных в основании модуля решает, можно ли размазать модуль на потоки/машины/дата-центры.
🎓 Источник: Архитектурный подход к программированию
- 📅 2018-09-25 · YouTube
- Тезисы:
- Архитектура нужна когда появляется сложность — за пределы одного часа работы одного программиста
- Давать имена — одна из важнейших и сложнейших архитектурных задач
- Архитектура = принятие решений: организация данных, обработка ошибок, безопасность