разработка

Бэкенд-архитектура: от монолита до микросервисов и Senior-стека

Разбираем фундаментальные принципы бэкенд-архитектуры: масштабирование, чистая архитектура, SOLID и полный стек Senior-разработчика. Конкретика без воды.

Бэкенд-архитектура: от монолита до микросервисов и Senior-стека

Бэкенд-архитектура: от монолита до микросервисов и Senior-стека

Мы изучили три объёмных материала — суммарно около 2,5 часов — по теме бэкенд-разработки: один разбирал масштабируемые серверные системы через живые схемы и код на Go, второй прогнал все 32 главы «Чистой архитектуры» Роберта Мартина и честно отделил полезное от воды, третий выстроил полный технологический стек от Junior до Senior с примерами из реальных коммерческих проектов.

Аудитория у всех трёх материалов примерно одна — разработчики, которые уже умеют писать код, но хотят понять, как системы устроены изнутри. Почему одни проекты легко масштабируются, а другие рассыпаются при первой же нагрузке. Как принимать архитектурные решения осознанно, а не методом копипаста из Stack Overflow.

Главный вывод, который прослеживается через все три источника: архитектура — это управление изменениями и зависимостями, а не выбор модного фреймворка или добавление Kafka ради Kafka.


Одиночный сервер — архитектурная ловушка

Разберём по порядку, с чего начинается любая проблема масштабирования.

Типичная отправная точка выглядит так: есть один сервер, данные хранятся локально на нём же. Всё работает, пока нагрузка невысокая и сервер не падает. Как только что-то идёт не так — данные теряются, система недоступна, горизонтальное масштабирование невозможно. Формулировка, которая хорошо описывает проблему:

Data coupled to the machine = no scale, no resilience

Привязка данных к конкретной машине — это не просто плохая практика. Это архитектурное решение, которое закрывает сразу несколько дверей одновременно. Нельзя добавить второй сервер, потому что непонятно, где тогда жить данным. Нельзя перезапустить инстанс без риска потери состояния. Нельзя обеспечить отказоустойчивость.

Решение очевидное, но важно понять его логику: данные должны быть отделены от серверного процесса. Сервер — это вычислительный ресурс, а не хранилище. Как только эти два понятия разделяются — появляется возможность запускать несколько серверных инстансов и управлять ими независимо.


Балансировщик нагрузки: как это работает на практике

После разделения сервера и данных следующий шаг — балансировщик нагрузки (Load… (17:40) ▶ 17:40

После разделения сервера и данных следующий шаг — балансировщик нагрузки (Load Balancer). Он принимает все входящие запросы и распределяет их между несколькими серверными инстансами.

Простейшая схема выглядит так:

User → Load Balancer → [Server 1, Server 2, Server 3] → Database

Алгоритм распределения по умолчанию — round-robin: запросы идут к серверам поочерёдно. Это позволяет: - добавлять новые инстансы без остановки системы - убирать инстансы для обслуживания без простоя - автоматически масштабировать — в том числе через контейнеризацию

На практике балансировщик часто реализуется через Nginx. Это не только веб-сервер — это полноценный reverse proxy, который принимает трафик из интернета, скрывает реальные IP-адреса и порты внутренних сервисов и передаёт запросы дальше по правилам.

В Nginx можно настроить: - ограничение размера загружаемых файлов (классическая ловушка для тестировщиков) - таймауты на длительность запроса - rate limiting — ограничение числа запросов с одного IP

Реальный кейс: тестировщик получает ошибку при загрузке файла. Первый инстинкт — искать баг в коде. На самом деле проблема в том, что Nginx режет файлы выше определённого размера — меняется одна строка конфига, всё работает. Умение быстро локализовать источник проблемы экономит несколько часов работы команды.

Sticky Sessions — когда stateless не работает

Балансировщик нагрузки хорошо работает, пока серверы stateless — то есть не хранят состояние сессии. Как только сессия привязывается к конкретному серверу, возникает задача sticky sessions: балансировщик должен направлять конкретного пользователя всегда на один и тот же инстанс.

Реализуется через cookie: при первом запросе балансировщик устанавливает cookie… (12:51) ▶ 12:51

Реализуется через cookie: при первом запросе балансировщик устанавливает cookie с идентификатором сервера, при следующих запросах читает его и маршрутизирует соответственно. Практический пример — код на Go с Nginx демонстрирует именно такую механику: HTTP-обработчик извлекает данные сессии из cookie и работает с ними.

Sticky sessions — это архитектурный компромисс. С одной стороны, решает проблему состояния. С другой — частично нивелирует преимущества балансировки: если один сервер перегружен «залипшими» пользователями, другие простаивают.


Чистая архитектура: что реально работает из книги Мартина

Теперь поднимемся на уровень выше — от инфраструктурных решений к архитектурным принципам.

Центральный тезис «Чистой архитектуры» Роберта Мартина звучит так:

Архитектура — это не выбор технологии. Это совокупность решений, которые позволяют системе легко изменяться.

Всё внешнее — база данных, UI, фреймворк, операционная система, сеть — это детали. Единственное, что составляет настоящее ядро системы — бизнес-правила. Расчёт стоимости заказа, применение скидок, проверка наличия товара — вот что система защищает в первую очередь.

Архитектура описывается как набор концентрических слоёв. Зависимости направлены строго внутрь — внешние слои знают о внутренних, но не наоборот:

[Фреймворки и драйверы]
    ↓
[Интерфейс-адаптеры]
    ↓
[Use Cases — сценарии использования]
    ↓
[Entities — бизнес-правила]

Файл index.ts или main.py — точка входа — находится на самом внешнем уровне. Именно он знает, какой фреймворк используется, какая БД подключена, какие сервисы задействованы. Это единственное место, где «грязным» деталям позволено контактировать с бизнесом — но только через абстракции.

Кричащая архитектура — и почему её сложно выдержать

Хорошая структура папок должна говорить о бизнесе, а не о технологии. Сравни два варианта:

Плохо:

/controllers
/services
/repositories

Хорошо:

/orders
/payment
/customers
/catalog
/shipping

В первом случае структура говорит о техническом слое. Во втором — о том, чем занимается приложение. На практике жёсткое следование этому принципу может привести к неудобному дроблению, особенно в больших проектах — здесь нужно находить баланс.

Где Мартин расходится с реальностью

Честный разбор книги выявил несколько мест, где теория плохо ложится на практику.

Идея о полной независимости от фреймворка — утрированная. Если проект построен на NestJS, просто «вынуть» фреймворк не получится: архитектура NestJS сама диктует структуру. Рабочий ориентир: если можно вытащить фреймворк минимальными усилиями и прийти к работающей системе — это уже хороший результат.

Принцип YAGNI — «You Are Not Gonna Need It» — важнее, чем может показаться. Не создавай абстракции раньше времени. Оверинжиниринг убивает скорость разработки не хуже технического долга.


SOLID на реальных примерах

Из всего содержания «Чистой архитектуры» именно принципы SOLID — наиболее применимая часть. Разберём по пунктам.

S — Single Responsibility Principle

Распространённая трактовка «класс делает одно дело» — слишком упрощённая. Точная формулировка: у модуля должна быть только одна причина для изменения.

Нарушение: класс UserService создаёт пользователя, валидирует, сохраняет в базу, генерирует отчёт и отправляет email. Логика email-рассылки нужна не только для пользователей — любое её изменение затрагивает весь сервис.

Решение: разделить на классы по зонам ответственности.

O — Open/Closed Principle

Сущности открыты для расширения, но закрыты для изменения. (19:13) ▶ 19:13

Сущности открыты для расширения, но закрыты для изменения. Для добавления нового типа скидки создаётся новый класс — в существующий код лезть не нужно.

L — Liskov Substitution Principle

Объект дочернего класса заменяет родительский без побочных эффектов. (14:53) ▶ 14:53

Объект дочернего класса заменяет родительский без побочных эффектов. Пример правильной реализации: интерфейс PaymentGateway корректно реализуют Stripe, PayPal, Crypto — каждый полностью закрывает контракт родителя.

I — Interface Segregation Principle

Клиент не должен зависеть от методов, которые не использует. Решение: дробить большой интерфейс на мелкие — Workable, Eatable, Sleepable. Каждый класс реализует только нужное.

D — Dependency Inversion Principle

UserService работает с абстракцией UserRepository. Конкретные реализации — MongoRepository, RedisRepository — зависят от абстракции, а не наоборот. Поток зависимостей — сверху вниз, от политики к деталям.


Полный стек бэкенд-разработчика: что нужно знать на каждом уровне

Теперь — конкретика по инструментам. Бэкенд-разработка — это не «написать API на Python». Это понимание того, как данные хранятся, обрабатываются, передаются и защищаются в распределённых системах. Разберём стек по блокам.

Языки и базы данных

Выбор языка на старте — не главное. С опытом приходит понимание: в разных языках встречаются одни и те же концепции и паттерны. Python — хороший старт: много вакансий, простой синтаксис. Go — язык, где разработчики чаще работают со стандартными библиотеками. Java — банковский сектор в России, где уже на Junior-позиции требуется знание Kafka.

По базам данных: - PostgreSQL — основная БД на российском рынке, используется на большинстве проектов - MongoDB — когда сущности имеют разное количество полей и хранить таблицу со 100 столбцами нерационально - ClickHouse — колоночная БД от Яндекса для аналитики и больших массивов данных, диалект SQL с расширенными возможностями

Транзакции и ACID — обязательные концепции для любого уровня. Знание размера полей в байтах при проектировании схемы — не педантизм, а практическая необходимость, когда речь идёт о предотвращении роста базы до нескольких терабайт. Иногда осознанное нарушение нормальных форм — хранение JSON или массива значений в одной ячейке PostgreSQL — оправданное решение, если задача это требует.

CPU-bound vs I/O-bound: фундаментальный разрыв

Это один из тех теоретических блоков, который часто отсутствует даже у Middle-разработчиков — и именно его отсутствие приводит к выбору неподходящих инструментов.

CPU-bound задачи — процессор активно занят вычислениями: рендеринг, обработка видео, криптографические операции, машинное обучение. Узкое место — сам процессор.

I/O-bound задачи — приложение отправляет запрос к внешнему ресурсу (база данных, кэш, брокер, внешний API) и ждёт ответа. В это время процессор нашего сервера практически свободен — он ждёт, пока ответит база или сеть. Именно на этом ожидании строится асинхронное программирование: пока один запрос ждёт ответа от БД, обрабатываем другой.

Большинство веб-приложений — I/O-bound. Если разработчик это понимает, он правильно выбирает подход к конкурентности и не пытается решить задачи ожидания методами параллельных вычислений.

Брокеры сообщений

Суть простая: сервис биллинга принял платёж и публикует событие в брокер. Его независимо читают сервис уведомлений, сервис сборки заказа, сервис логистики и сервис антифрода. Никакой прямой связи между сервисами — только через брокер.

  • RabbitMQ — умеренная нагрузка, после обработки сообщение удаляется из очереди
  • Kafka — высоконагруженные системы, сообщения не удаляются (возможен replay), лучше масштабируется
  • NATS — ещё один вариант, занимает нишу между ними по сложности

На Python знание брокеров не обязательно на Junior-позиции. На Java — обязательно уже с первой работы.

Кэширование и S3

Кэширование экономит обращения к базе данных и ускоряет ответы. Но не все данные нужно кэшировать — одни должны быть всегда свежими, другие спокойно живут в кэше месяцами.

S3-хранилище — для файлов: аватарки, документы, бэкапы. Данные реплицируются в трёх дата-центрах одновременно. Практический нюанс: файл можно отдать клиенту двумя способами.

  1. Через бэкенд: файл идёт S3 → бэкенд → клиент. Файл проходит путь дважды, нагружает сервер.
  2. Через presigned URL: бэкенд генерирует временную ссылку, клиент скачивает файл напрямую из S3. Бэкенд не задействован. Второй способ — правильный.

По стоимости: бэкапы базы данных за 14 дней обходятся в 20 копеек в месяц. За год хранения — 4 рубля. Это не опечатка.

Авторизация и аутентификация

Они встречаются в каждом приложении — поэтому это не опциональный навык.

JWT-токен — строка в кодировке Base64, которую можно декодировать. Важно понимать разницу: декодировать ≠ дешифровать. Подпись токена проверяется на сервере, но payload читается без ключа. Не храните в JWT чувствительные данные.

Пароли хешируются, а не шифруются. Если хакер получит базу данных, у него будут только хеши — использовать их напрямую невозможно.

Двухфакторная аутентификация через Google Authenticator: генерируется QR-код, пользователь сканирует его телефоном. Код живёт 30 секунд, затем обновляется. Реализуется стандартными библиотеками на любом языке — сложного здесь мало, но понимание механики нужно.

В контексте микросервисной архитектуры Auth выделяется в отдельный сервис… (27:17) ▶ 27:17

В контексте микросервисной архитектуры Auth выделяется в отдельный сервис внутри VPC. Схема: запрос от клиента идёт через Gateway → попадает в изолированную сеть → Auth-сервис проверяет JWT → запрос маршрутизируется к нужному микросервису. Внешний мир не видит ничего за Gateway.

Docker, CI/CD и Kubernetes

Docker Compose — минимальный стандарт: поднять всё окружение (базу, кэш, приложение) одной командой. Без этого команда тратит часы на настройку локальной среды.

В Dockerfile можно кэшировать слои зависимостей — сборка сокращается с 20 минут до 5. Размер образа важен: разница между 3 ГБ и 500 МБ существенна при деплое.

CI/CD-пайплайн в минимальном виде: 1. Забрать код из репозитория 2. Прогнать тесты 3. Собрать Docker-образ 4. Положить в реестр (Harbor, Nexus) 5. Задеплоить на сервер

Ускорение пайплайна с 10 до 7 минут — значимый результат. Не потому что 3 минуты много сами по себе, а потому что разработчик теряет состояние фокуса каждый раз, когда переключается на ожидание.

Kubernetes — для разработчика нужен минимум: понимать что такое поды и ReplicaSet, уметь смотреть логи конкретного пода, уметь перезагрузить под. Helm chart — важно понимать, откуда в контейнер попадают секреты (логины, пароли, адреса БД).

Мониторинг

Grafana + Prometheus — стандартный стек для метрик. Grafana Loki — для хранения логов. Для работы с логами существуют альтернативы: Elasticsearch + Kibana, Graylog.

Алерты в Telegram при ошибках на бэкенде — не роскошь, а базовая гигиена. Команда видит уведомление сразу и может оценить критичность без постоянного мониторинга дашборда.


Практический раздел: что делать прямо сейчас

Если после прочтения хочется структурировать действия — вот конкретный порядок.

Если ты Junior или только переходишь к реальным проектам: - Разберись с разделением сервера и данных — задеплой простое приложение с внешней базой данных - Подними локальный Nginx как балансировщик между двумя инстансами одного приложения - Реализуй JWT-аутентификацию с нуля — без библиотек, которые всё прячут - Изучи Docker Compose до уровня «поднять весь проект одной командой»

Если ты Middle и хочешь расти: - Проверь свой текущий проект на нарушения SOLID — особенно SRP. Сколько причин для изменения у каждого сервиса? - Нарисуй архитектурную схему текущей системы в Excalidraw. Если не можешь — это сигнал - Попробуй добавить в проект брокер сообщений для одного реального сценария (не ради факта добавления) - Настрой CI/CD для личного проекта через GitHub Actions или GitLab CI

Для понимания системного дизайна: - Прочитай SOLID не как набор правил, а как… (03:19) ▶ 03:19

Для понимания системного дизайна: - Прочитай SOLID не как набор правил, а как инструмент управления изменениями — спроси себя перед каждым классом: сколько причин его изменить? - Изучи разницу CPU-bound vs I/O-bound применительно к своему основному языку - Разберись с presigned URLs в S3 — это одно из тех решений, которые сразу улучшают архитектуру

Чек-лист для ревью архитектуры: - [ ] Данные отделены от серверного процесса? - [ ] Зависимости направлены от деталей к бизнесу, а не наоборот? - [ ] Каждый модуль имеет одну причину для изменения? - [ ] Есть ли абстракции, которые созданы «на будущее» и не используются сейчас? - [ ] Понятна ли структура папок человеку, который не знает стек проекта? - [ ] Можно ли локализовать проблему — Nginx, Kubernetes, CI/CD — без перебора всего стека?


Что мы заметили: где подходы сходятся, а где расходятся

Мы изучили три источника с разными углами зрения — инфраструктурный, теоретический и карьерный — и несколько наблюдений оказались неожиданными.

Сходятся все три источника в одном: технология — это деталь, а не архитектура. Мартин пишет о бизнес-правилах как ядре системы. Инфраструктурный разбор показывает, что балансировщик, VPC и микросервисы — это ответ на конкретные проблемы, а не модный стек. Senior-разработчик в третьем источнике прямо говорит: добавлять Kafka или Redis «ради самого факта» — неправильно. Все три источника транслируют один и тот же принцип через разные языки.

Расходятся в вопросе монолит vs микросервисы. Подход А (теоретический) утверждает: микросервисы не гарантируют хорошую архитектуру — можно писать плохой проект на микросервисах и хороший на монолите. Подход Б (инфраструктурный) строит маршрут именно к микросервисной архитектуре с VPC и отдельными базами данных как к «зрелому» решению. Это не противоречие — скорее два разных вопроса: «хорошо ли написан код?» и «хорошо ли масштабируется система?». Можно получить любую комбинацию.

Интересная точка напряжения — тестирование. В теоретическом разборе тесты названы «ещё одним клиентом сервиса» — элегантная концепция. В практическом стеке тестирование откровенно описывается как то, что в одиночных проектах игнорируется, но в команде становится критическим. Более консервативный взгляд: тесты важны, но их место в архитектуре — прикладное, а не философское.

Совпадение, которое нас удивило: оба источника, разбирающих архитектуру, независимо приходят к теме изоляции зависимостей — только с разных сторон. Первый показывает это через VPC и Gateway в инфраструктуре. Второй — через принцип инверсии зависимостей и слои чистой архитектуры в коде. Суть одна: внутренние сущности не должны знать о внешних деталях.


Архитектура как навык, а не набор паттернов

Три часа контента по бэкенд-архитектуре дают один устойчивый вывод: хорошие решения принимаются не из-за знания паттернов, а из-за понимания, какую конкретную проблему решает каждый элемент системы.

Балансировщик нагрузки существует потому, что одна машина не может расти бесконечно. VPC — потому что внутренние сервисы не должны быть доступны напрямую снаружи. SRP — потому что код с несколькими причинами для изменения ломается при любом из них. Kafka вместо RabbitMQ — потому что нужен replay и горизонтальное масштабирование, а не просто очередь.

Когда каждый элемент стека понимается через задачу, которую он решает — архитектурные решения перестают быть абстрактными. Они становятся ответами на конкретные вопросы. Начни с того, где твои текущие системы дают трещины, и двигайся оттуда.

Использованные видео

Хотите такой же разбор для своего видео?

ZUB-AI проанализирует видео с YouTube, RuTube или VK и пришлёт структурированный отчёт. Первый анализ — бесплатно.

Попробовать ZUB-AI →