Как устроен бэкенд без магии готовых фреймворков
Недавно на собеседовании я спросил кандидата, что происходит с активными HTTP-запросами, когда контейнер с Node.js приложением перезапускается в Kubernetes. Человек уверенно писал на NestJS три года, строил микросервисы, но вопрос поставил его в тупик. Он никогда не задумывался о сигналах операционной системы, таймаутах базы данных и остановке сервера. Фреймворк прятал эти детали под капот, пока в продакшене не начинали сыпаться пятисотые ошибки во время каждого деплоя.
Мы привыкли собирать бэкенд из готовых кирпичиков. Добавил пару декораторов, подключил ORM, нажал кнопку, и API отвечает клиентам. Трудности начинаются, когда готовые абстракции дают сбой или сервис упирается в пределы по памяти и процессору.
Эту проблему разбирает репозиторий Backend from First Principles, созданный разработчиком DsThakurRawat. Автор собрал инженерный справочник, где раскладывает серверную разработку на базовые механики.
Что внутри репозитория
Проект разбит на 24 темы. В них собраны конспекты, архитектурные схемы и примеры кода на JavaScript. У репозитория около трехсот звезд на GitHub и отдельный сайт с документацией. Это не раздутый коммерческий курс, а открытые заметки инженера, который наводит порядок в собственных знаниях.
Материал выстроен последовательно. Сначала идут протоколы, сериализация и маршрутизация, затем архитектурные слои, а ближе к концу автор переходит к распределенным системам, брокерам сообщений и масштабированию.
Главная идея проекта проста: когда понимаешь физику процесса, любой новый фреймворк осваивается за пару вечеров.
Четыре темы из справочника
Вместо сухого пересказа оглавления разберем четыре темы, где автор затрагивает насущные инженерные задачи.
Корректное завершение работы приложения
В типичных руководствах для новичков сервер запускают через app.listen(3000), и на этом код заканчивается. В продакшене сервер постоянно перезапускается при деплое, нехватке памяти или плановом обслуживании ноды.
В репозитории детально показано, как организовать graceful shutdown. Когда процесс получает сигнал SIGTERM от операционной системы, приложение выполняет цепочку шагов:
- перестает принимать новые входящие HTTP-соединения;
- дает время завершиться тем запросам, которые уже находятся в обработке;
- закрывает пулы соединений с базой данных и кэшем;
- сбрасывает буферы логов на диск перед выходом.
Автор приводит наглядный пример перехвата системных сигналов в Node.js:
const server = app.listen(PORT, () => {
console.log(`Server running on port ${PORT}`);
});
function shutdown(signal) {
console.log(`Received ${signal}. Shutting down gracefully...`);
server.close(() => {
console.log('HTTP server closed');
// Закрываем подключения к базе данных
db.pool.end(() => {
console.log('Database connections closed');
process.exit(0);
});
});
// Принудительное завершение, если запросы зависли
setTimeout(() => {
console.error('Forced shutdown due to timeout');
process.exit(1);
}, 10000);
}
process.on('SIGTERM', () => shutdown('SIGTERM'));
process.on('SIGINT', () => shutdown('SIGINT'));
Такой подход спасает от оборванных транзакций и испорченных пользовательских данных во время обновления сервисов.
Сериализация и нагрузка на процессор
Многие считают, что JSON.stringify() и JSON.parse() работают почти бесплатно. На десяти запросах в секунду разница действительно неощутима. Когда через сервис идут тысячи запросов, текстовый парсинг начинает отъедать ощутимую долю процессорного времени.
В разделе про сериализацию автор разбирает различия между форматами:
- текстовые структуры вроде JSON;
- бинарные протоколы вроде Protocol Buffers;
- накладные расходы на валидацию и трансформацию входящих схем;
- влияние объема передаваемых данных на пропускную способность сети.
Там хорошо показано, почему для внутреннего взаимодействия микросервисов инженеры выбирают gRPC и бинарные форматы вместо обычного REST на JSON.
Архитектурные слои без лишних усложнений
Нередко разработчики смешивают логику: пишут SQL-запросы прямо в контроллерах или складывают валидацию параметров в сервисный слой.
В репозитории разобран классический многослойный подход:
- контроллеры принимают входящий HTTP-запрос и формируют ответ;
- сервисы реализуют бизнес-правила и логику приложения;
- репозитории отвечают за взаимодействие с базой данных;
- промежуточные обработчики (middlewares) проверяют авторизацию, логируют метрики и обогащают контекст запроса.
Четкое разделение слоев помогает изолированно тестировать бизнес-логику без поднятия базы данных и реальных сетевых вызовов.
Асинхронные задачи и очереди
Если пользователю нужно сгенерировать сложный PDF-отчет или обработать видео, делать это внутри HTTP-запроса нельзя. Клиент получит ошибку по таймауту, а рабочий поток сервиса будет заблокирован.
В руководстве разобран паттерн работы с фоновыми задачами:
- сохранение задания в очередь брокера сообщений;
- возврат клиенту ответа со статусом 202 Accepted и идентификатором задачи;
- асинхронное исполнение воркерами в отдельном пуле;
- стратегии повторных попыток при сбоях и отправка проблемных сообщений в Dead Letter Queue.
Недостатки и шероховатости
Проект относительно молодой, поэтому в нем есть ограничения.
Практически весь код написан на JavaScript. Если ваш основной стек — Go, Java, Rust или C#, придется мысленно адаптировать примеры под типизацию и особенности своей платформы. Автор прямо пишет в README, что ждет помощь сообщества с примерами на других языках.
Некоторые темы пока больше похожи на краткие тезисы, чем на исчерпывающие статьи. Хотелось бы видеть больше разборов реальных инцидентов и тестов производительности с графиками.
Кому пригодится репозиторий
Этот проект можно использовать в четырех практических ситуациях:
- Начинающим разработчикам, которые хотят разобраться в сетевых протоколах, сигналах ОС и структуре баз данных.
- Инженерам при подготовке к секциям System Design и техническим собеседованиям.
- Разработчикам, которые переходят от монолитной архитектуры к распределенным сервисам.
- Тимлидам для составления плана развития или онбординга новых сотрудников в команду.
Что в итоге
Справочник DsThakurRawat помогает взглянуть на бэкенд как на понятный набор инженерных принципов, а не магию библиотек. Не обязательно штудировать его целиком от начала до конца.
Добавьте репозиторий в закладки и открывайте нужный раздел, когда проектируете архитектуру, настраиваете кэш в Redis или готовите микросервис к выкатке в Kubernetes.
