Профессия Backend-разработчик: от основ до продакшена

Курс знакомит с ключевыми навыками backend-разработки: проектированием API, работой с базами данных, безопасностью и производительностью. Вы научитесь строить надежные серверные приложения, тестировать их и готовить к деплою и сопровождению.

1. Роль backend-разработчика и основы HTTP

Роль backend-разработчика и основы HTTP

Зачем нужен backend

Backend — это часть приложения, которая работает на сервере и отвечает за данные и бизнес-логику. Если frontend показывает интерфейс пользователю, то backend:

  • принимает запросы от клиентов (браузер, мобильное приложение, другие сервисы)
  • проверяет права доступа и корректность данных
  • выполняет бизнес-правила (например, расчёт скидки, оформление заказа)
  • читает и записывает данные в хранилища (база данных, кэш, очередь)
  • отдаёт ответ в понятном клиенту формате (часто JSON)
  • Backend-разработчик делает так, чтобы система была:

  • корректной (правильные ответы и данные)
  • надёжной (работает стабильно, восстанавливается после сбоев)
  • быстрой (минимальные задержки)
  • безопасной (защита данных и доступа)
  • масштабируемой (выдерживает рост нагрузки)
  • Где backend находится в архитектуре

    Типичная схема выглядит так: клиент отправляет запрос, инфраструктура маршрутизирует его в backend, backend обращается к данным и возвращает ответ.

    !Схема показывает, как запрос проходит через backend к данным и возвращается ответом

    Важно понимать роли компонентов:

  • Клиент — инициирует запрос (например, открыть страницу или получить список товаров).
  • API / Backend — принимает запрос и выполняет бизнес-логику.
  • База данных — постоянное хранение данных (пользователи, заказы).
  • Кэш — ускоряет доступ к часто используемым данным.
  • Очередь сообщений — помогает выполнять тяжёлые задачи асинхронно (например, отправку писем).
  • Что такое HTTP и почему он важен

    HTTP (HyperText Transfer Protocol) — протокол, по которому клиент и сервер обмениваются сообщениями по сети.

    Для backend-разработчика HTTP — базовый язык общения с внешним миром, потому что:

  • веб и большинство API используют HTTP
  • от HTTP зависят маршрутизация запросов, кэширование, безопасность, наблюдаемость
  • правильная работа с методами, статусами и заголовками напрямую влияет на качество API
  • Официальная спецификация современной версии HTTP описана в RFC 9110: RFC 9110: HTTP Semantics

    Практическая документация с примерами: MDN Web Docs: HTTP

    Модель «запрос–ответ»

    HTTP работает по модели request/response:

  • Клиент отправляет HTTP-запрос на конкретный адрес.
  • Сервер обрабатывает запрос.
  • Сервер возвращает HTTP-ответ.
  • При этом HTTP сам по себе не хранит состояние между запросами: каждый запрос — отдельное сообщение. Это свойство называют stateless. Если приложению нужно «помнить» пользователя между запросами, обычно используют токены, сессии и cookies (к этому мы ещё вернёмся в следующих темах).

    Из чего состоит HTTP-запрос

    HTTP-запрос включает:

  • Метод — что клиент хочет сделать.
  • URL — куда обращаемся.
  • Заголовки (headers) — метаданные (формат данных, авторизация, кэширование и т.д.).
  • Тело (body) — данные запроса (не всегда есть).
  • Пример (упрощённо):

    URL: путь и параметры

    URL часто состоит из:

  • Path (путь) — например, /orders/123
  • Query parameters (параметры запроса) — часть после ?, например, /products?category=books&page=2
  • Путь чаще описывает ресурс (например, заказ), а query-параметры — фильтрацию, поиск, пагинацию.

    HTTP-методы

    Метод показывает намерение запроса. Самые используемые:

    | Метод | Типичное назначение | Есть ли body | Пример | |---|---|---:|---| | GET | Получить данные | Обычно нет | GET /products | | POST | Создать ресурс или запустить операцию | Да | POST /orders | | PUT | Полностью заменить ресурс | Да | PUT /users/10 | | PATCH | Частично изменить ресурс | Да | PATCH /users/10 | | DELETE | Удалить ресурс | Обычно нет | DELETE /orders/123 |

    Безопасность и идемпотентность методов

    Эти свойства помогают проектировать API и понимать, как запросы могут повторяться.

  • Safe (безопасный) метод не должен изменять данные на сервере. Типичный safe-метод — GET.
  • Idempotent (идемпотентный) означает: повтор одного и того же запроса приводит к тому же эффекту, что и один запрос.
  • Примеры:

  • GET /products идемпотентен: сколько ни повторяй — состояние сервера не меняется.
  • DELETE /orders/123 обычно идемпотентен: первый раз удалит, последующие вернут «уже удалено/не найдено», но итог один — заказа нет.
  • POST /orders часто не идемпотентен: повтор может создать два заказа.
  • Из чего состоит HTTP-ответ

    HTTP-ответ включает:

  • Status code (код состояния) — результат обработки.
  • Заголовки (headers) — метаданные ответа.
  • Тело (body) — данные ответа (например, JSON).
  • Пример (упрощённо):

    Коды состояния HTTP

    Код состояния — это число, по которому клиент быстро понимает результат.

    | Диапазон | Смысл | Примеры | |---:|---|---| | 1xx | Информация (редко в прикладных API) | 100 Continue | | 2xx | Успех | 200 OK, 201 Created, 204 No Content | | 3xx | Перенаправление | 301 Moved Permanently, 302 Found, 304 Not Modified | | 4xx | Ошибка клиента (запрос неверный/нет прав) | 400 Bad Request, 401 Unauthorized, 403 Forbidden, 404 Not Found, 409 Conflict | | 5xx | Ошибка сервера | 500 Internal Server Error, 502 Bad Gateway, 503 Service Unavailable |

    Важные различия:

  • 401 Unauthorized обычно означает «нужно аутентифицироваться» (например, нет токена).
  • 403 Forbidden означает «аутентификация есть, но прав недостаточно».
  • 404 Not Found — ресурс не найден.
  • Заголовки: что они решают

    Заголовки (headers) — это пары вида Имя: значение. Они передают контекст запроса/ответа.

    Часто используемые:

  • Content-Type — формат тела (например, application/json).
  • Accept — какие форматы клиент готов принять.
  • Authorization — данные для доступа (например, Bearer-токен).
  • Cache-Control — правила кэширования.
  • User-Agent — информация о клиенте.
  • Backend-разработчик обязан правильно выставлять заголовки, потому что они влияют на совместимость клиентов, безопасность и производительность.

    Тело сообщения и JSON

    Во многих API тело запроса и ответа — это JSON.

    Почему JSON популярен:

  • читается людьми
  • поддерживается почти везде
  • удобно описывает структуры данных (объекты, массивы)
  • Пример ответа:

    HTTPS: HTTP плюс шифрование

    На практике почти всегда используется HTTPS — это HTTP поверх шифрованного соединения TLS.

    HTTPS даёт:

  • конфиденциальность (данные нельзя просто «подсмотреть» в сети)
  • целостность (сложнее незаметно подменить данные)
  • подтверждение подлинности сервера (сертификат)
  • Для backend-разработчика это важно при работе с авторизацией, персональными данными и любыми платёжными/приватными сценариями.

    Что должен уметь backend-разработчик на уровне HTTP

    К концу изучения базовых основ HTTP вам нужно уверенно понимать и применять:

  • Как устроены запрос и ответ (метод, путь, заголовки, тело).
  • Как выбирать метод под задачу (GET/POST/PUT/PATCH/DELETE).
  • Как подбирать корректные коды ответа (2xx/4xx/5xx).
  • Почему Content-Type и другие заголовки критичны.
  • Чем отличается HTTP от HTTPS и почему шифрование обязательно в продакшене.
  • Дальше в курсе мы будем опираться на эти основы, когда перейдём к проектированию API, работе с базами данных, аутентификации, логированию, тестированию и деплою.

    2. Язык и фреймворк: структура проекта и паттерны

    Язык и фреймворк: структура проекта и паттерны

    Как эта тема связана с HTTP

    В предыдущей статье мы разобрали, что HTTP — это базовый способ общения клиента и сервера: запрос, обработка, ответ, статусы, заголовки.

    Язык и фреймворк — это инструменты, которые превращают эти правила в работающий код:

  • фреймворк принимает HTTP-запрос и маршрутизирует его в нужный обработчик
  • помогает провалидировать входные данные
  • даёт удобные механизмы для авторизации, логирования, обработки ошибок
  • упрощает работу с базой данных и внешними сервисами
  • Главная задача backend-разработчика здесь — сделать проект таким, чтобы его можно было развивать и поддерживать: добавлять новые эндпоинты, менять бизнес-логику, масштабировать команду.

    Язык и фреймворк: что вы выбираете на практике

    На уровне профессии backend-разработчика важно понимать: большинство фреймворков решают одни и те же классы задач, но по-разному организуют код.

    Примеры распространённых стеков:

  • Java/Kotlin: Spring Boot (Spring Boot Reference Documentation)
  • Python: Django (Django documentation), FastAPI (FastAPI documentation)
  • Node.js: Express (Express documentation), NestJS (NestJS documentation)
  • Go: стандартный net/http + роутеры/DI по необходимости
  • C#: ASP.NET Core
  • Что влияет на выбор:

  • требования к производительности и задержкам
  • экосистема библиотек (БД, очереди, наблюдаемость)
  • опыт команды и найм
  • зрелость подходов к тестированию и деплою
  • Но независимо от стека, вам нужен предсказуемый скелет проекта и понятные архитектурные границы.

    Структура проекта: зачем она нужна

    Структура проекта отвечает на вопросы:

  • где находится код, который принимает HTTP
  • где находится бизнес-логика
  • где код работы с базой данных
  • где конфигурация
  • где тесты
  • Если структура не определена, появляются типичные проблемы:

  • бизнес-правила размазываются по контроллерам и SQL
  • сложно тестировать (нельзя подменить БД/внешний сервис)
  • изменения ломают несвязанные части
  • каждый разработчик пишет «как привык», проект становится неоднородным
  • Базовая идея: разделение на слои

    Один из самых практичных подходов для старта — слоистая архитектура (layered architecture). Она делит код на зоны ответственности.

    !Диаграмма показывает, как запрос проходит через контроллер, сервис и репозиторий

    Типичные слои:

  • HTTP-слой (контроллеры/хендлеры): принимает запрос, извлекает параметры, запускает нужную операцию, формирует HTTP-ответ.
  • Слой бизнес-логики (сервисы/use cases): правила предметной области (скидки, статусы заказов, ограничения).
  • Слой данных (репозитории/DAO): чтение и запись в БД, кэш, очереди.
  • Инфраструктура: клиенты внешних API, брокеры сообщений, реализация репозиториев, конфигурация.
  • Ключевая мысль: HTTP и БД — это детали доставки и хранения. Бизнес-правила должны жить отдельно.

    Пример универсального каркаса проекта

    Названия папок различаются по языкам, но смысл часто одинаков.

    Один из удачных нейтральных вариантов:

  • cmd/ или app/ — точка входа (запуск сервера)
  • internal/ или src/ — основной код приложения
  • transport/ или api/ — HTTP-слой (роуты, контроллеры, middleware)
  • domain/ — сущности предметной области и правила, которые почти не зависят от фреймворка
  • application/ — сценарии (use cases), сервисы, транзакции
  • infrastructure/ — БД, очереди, клиенты внешних сервисов, реализация репозиториев
  • config/ — конфигурация приложения
  • migrations/ — миграции схемы БД
  • tests/ — тесты
  • Важно: это не «единственно правильная» структура. Но у неё есть проверяемое свойство — каждой папке можно объяснить ответственность одним предложением.

    Паттерны, которые встречаются почти в любом backend

    MVC как стартовая модель

    MVC (Model–View–Controller) часто объясняют в веб-разработке первым.

    Для backend-API (без серверных HTML-шаблонов) обычно применяют упрощённую интерпретацию:

  • Controller — HTTP-обработчик
  • Model — данные и доменные сущности
  • View — формирование ответа (обычно JSON)
  • Полезно знать MVC как термин, но для сложных сервисов его часто недостаточно, поэтому вводят явные слои service/use case и repository.

    Middleware / Filters / Interceptors

    Почти любой фреймворк позволяет подключать код, который выполняется до и/или после обработчика.

    Типичные задачи middleware:

  • логирование запроса и времени ответа
  • проверка авторизации
  • ограничение частоты запросов (rate limiting)
  • добавление requestId для трассировки
  • обработка ошибок единообразно
  • Это напрямую связано с HTTP: middleware часто работает с заголовками (Authorization, X-Request-Id), кодами ответов и форматами ошибок.

    Dependency Injection (внедрение зависимостей)

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

    Dependency Injection (DI) — способ передавать зависимости извне, а не создавать их прямо внутри бизнес-кода.

    Зачем DI:

  • проще тестировать (можно подставить фейковую реализацию)
  • проще менять инфраструктуру (другая БД, другой клиент)
  • меньше связности между модулями
  • Хорошее введение в идею DI: Inversion of Control Containers and the Dependency Injection pattern

    Service и Use Case

    Service или Use Case — слой, который отвечает за выполнение конкретного сценария.

    Пример сценария: создать заказ.

    Что обычно находится в use case:

  • валидация бизнес-ограничений (не «пустой email», а «нельзя создать заказ без активного пользователя»)
  • вызовы репозиториев и внешних сервисов
  • управление транзакцией (если требуется)
  • формирование результата, который вернётся в HTTP-слой
  • Важно: контроллер должен быть тонким. Если в контроллере много условий и бизнес-правил, поддержка быстро усложняется.

    Repository (репозиторий)

    Repository — паттерн, который скрывает детали хранения данных за интерфейсом методов.

    Например, вместо SQL в сервисе:

  • сервис вызывает ordersRepository.save(order)
  • реализация репозитория уже решает, как именно писать в БД
  • Описание паттерна: Repository

    DTO (Data Transfer Object)

    DTO — структуры данных для передачи через границы системы.

    Чаще всего:

  • Request DTO — то, что приходит в HTTP (JSON)
  • Response DTO — то, что отдаём клиенту
  • Зачем DTO, если есть доменные сущности:

  • доменная модель не обязана совпадать с внешним контрактом API
  • API-формат меняется по требованиям клиентов, а доменные правила должны быть стабильнее
  • проще валидировать входные данные отдельно
  • Более продвинутая граница: Hexagonal / Clean Architecture

    Когда проект растёт, часто переходят от «слоёв» к архитектурам, где явно выделяются границы и порты/адаптеры.

    Идея: бизнес-логика не должна зависеть от того, HTTP у вас или gRPC, PostgreSQL или MongoDB.

    Один из классических источников по hexagonal architecture: Hexagonal Architecture

    Практический смысл для backend-разработчика:

  • вы можете заменить HTTP на очередь сообщений для части сценариев, не переписывая домен
  • вы можете тестировать use cases без поднятия веб-сервера
  • вы можете сменить библиотеку работы с БД, не ломая слой бизнес-логики
  • Конфигурация и окружения: то, что быстро отличает учебный проект от продакшена

    Даже небольшой backend почти всегда работает в нескольких окружениях:

  • локальная разработка
  • тестовое окружение (CI)
  • staging
  • production
  • Правила, которые стоит принять рано:

  • конфигурация (строки подключения, ключи, URL внешних сервисов) не должна хардкодиться в коде
  • секреты не должны храниться в репозитории
  • приложение должно легко запускаться в новом окружении
  • Хороший практический стандарт: The Twelve-Factor App

    Единый стиль ошибок и ответов

    Раз вы проектируете HTTP API, клиентам важна стабильность формата ошибок.

    Минимальный полезный подход:

  • всегда возвращать JSON для ошибок
  • иметь единый набор кодов и сообщений
  • различать ошибки клиента (4xx) и ошибки сервера (5xx) из предыдущей статьи
  • Пример структуры ошибки (общий, не привязан к фреймворку):

    Минимальный чеклист хорошего backend-проекта

  • HTTP-слой не содержит бизнес-правил, только обработку протокола
  • бизнес-логика оформлена в сервисы/use cases
  • доступ к данным спрятан за репозиториями
  • зависимости внедряются (DI) и подменяются в тестах
  • конфигурация вынесена во внешние источники (env/секреты)
  • ошибки стандартизированы и соответствуют HTTP-статусам
  • Эта база даст вам устойчивую платформу: дальше в курсе её можно расширять аутентификацией, транзакциями, миграциями, тестированием, логированием, метриками и деплоем — не превращая код в хаос.

    3. REST API и документация

    REST API и документация

    Как REST продолжает тему HTTP и структуры проекта

    В предыдущих статьях мы разобрали:

  • как устроен HTTP: методы, статусы, заголовки, тело запроса и ответа
  • как организовать backend-проект: разделить HTTP-слой, бизнес-логику и доступ к данным
  • REST API соединяет эти две темы: вы проектируете контракт поверх HTTP так, чтобы клиентам было удобно и предсказуемо пользоваться вашим сервисом, а внутри проекта этот контракт реализовывался чисто: контроллеры тонкие, use case понятные, репозитории изолированы.

    Что такое REST в практическом смысле

    REST (Representational State Transfer) часто воспринимают как набор правил “как делать API”. На практике для backend-разработчика REST API обычно означает:

  • вы работаете с ресурсами (users, orders, products)
  • у ресурса есть адрес (URL)
  • вы используете HTTP-методы по смыслу (GET, POST, PUT, PATCH, DELETE)
  • вы возвращаете корректные HTTP-статусы и стабильный формат данных
  • Важно: REST не “магия” и не обязательный стандарт. Это набор договорённостей, которые делают API понятным.

    Один из источников, где определены базовые ограничения REST: Architectural Styles and the Design of Network-based Software Architectures (Roy Fielding)

    Ресурсы, коллекции и URL

    Ресурс как предмет договора

    Ресурс в REST API это сущность, с которой работает клиент.

    Примеры ресурсов:

  • пользователь
  • заказ
  • товар
  • комментарий
  • Важно отличать ресурс от таблицы в базе данных: ресурс это часть внешнего контракта, а не “как у нас внутри хранится”.

    Коллекция и элемент

    Обычно используют:

  • коллекцию: /users
  • элемент коллекции: /users/{userId}
  • Хорошая практика:

  • существительные во множественном числе для коллекций: /orders, /products
  • вложенность только там, где она реально нужна: /users/{userId}/orders
  • Плохая практика:

  • глаголы в пути: /getUsers, /createOrder
  • Глагол уже есть в HTTP-методе.

    !Карта типовых эндпоинтов и связь методов с ресурсами

    Методы HTTP в REST: договорённости и типовые ответы

    Типовые операции CRUD

    | Операция | Метод | Пример URL | Что делает | |---|---|---|---| | Read (прочитать) | GET | /users/10 | Получить представление ресурса | | Create (создать) | POST | /users | Создать новый ресурс | | Replace (заменить) | PUT | /users/10 | Полностью заменить ресурс | | Update (изменить) | PATCH | /users/10 | Частично изменить ресурс | | Delete (удалить) | DELETE | /users/10 | Удалить ресурс |

    Связь с прошлой статьёй про HTTP:

  • GET остаётся safe (не меняет данные)
  • PUT и DELETE обычно идемпотентны
  • POST часто не идемпотентен
  • Какие статусы возвращать

    Типовой минимум:

  • 200 OK когда возвращаете тело ответа
  • 201 Created когда создали ресурс (часто вместе с заголовком Location)
  • 204 No Content когда успешно, но тело не нужно (например, DELETE)
  • 400 Bad Request когда запрос синтаксически неверный или не прошёл валидацию
  • 401 Unauthorized когда не хватает аутентификации
  • 403 Forbidden когда прав недостаточно
  • 404 Not Found когда ресурс не найден
  • 409 Conflict когда конфликт состояния (например, уникальность)
  • 500 Internal Server Error когда ошибка на стороне сервера
  • Пример создания ресурса:

    Форматы данных, ошибки и стабильность контракта

    JSON как формат по умолчанию

    Если вы используете JSON, важно всегда явно указывать:

  • Content-Type: application/json в ответе
  • Accept: application/json в запросе (если клиент умеет)
  • Единый формат ошибок

    Клиентам важно, чтобы ошибки были одинаковыми по форме во всех эндпоинтах.

    Рекомендуемый подход:

  • одинаковая “обёртка”
  • машинно-читаемый code
  • человеко-читаемый message
  • опциональные details для полей
  • Пример:

    Связь с архитектурой проекта:

  • HTTP-слой преобразует исключения и результаты use case в этот формат
  • бизнес-логика не должна “знать” про HTTP-статусы, но должна уметь различать типы ошибок (например, “не найдено”, “нельзя по правилам”, “конфликт”)
  • Фильтрация, сортировка и пагинация

    Query-параметры

    Для коллекций почти всегда нужны:

  • фильтры: /products?category=books
  • поиск: /products?query=tolkien
  • сортировка: /products?sort=price,-rating
  • пагинация: /products?page=2&pageSize=20
  • Хорошая практика:

  • документировать значения по умолчанию и ограничения (pageSize максимум)
  • возвращать мета-информацию о пагинации
  • Пример ответа с метаданными:

    Версионирование API

    Когда API используют клиенты, “ломающие” изменения становятся дорогими. Поэтому версионирование это способ управлять эволюцией контракта.

    Самый популярный на практике подход для публичных API:

  • версия в URL: /api/v1/users
  • Альтернативы:

  • версия в заголовке (сложнее в эксплуатации)
  • Хорошая практика:

  • не версионировать “на всякий случай”
  • заводить новую версию только для реально несовместимых изменений
  • поддерживать старую версию ограниченное время и объявлять сроки
  • Документация API: зачем она нужна в продакшене

    Документация это часть контракта. Без неё API превращается в “угадайку”:

  • сложно подключать новых клиентов и разработчиков
  • трудно поддерживать обратную совместимость
  • появляются разные трактовки форматов и статусов
  • Хорошая документация отвечает на вопросы:

  • какие есть эндпоинты и что они делают
  • какие параметры, заголовки и тела запросов допустимы
  • какие форматы ответов возможны
  • какие ошибки бывают и как их обрабатывать
  • как работает авторизация
  • OpenAPI (Swagger) как стандарт для описания REST API

    OpenAPI Specification это формат описания API, который понимают инструменты: генерация клиентов, интерактивная документация, проверки контракта.

    Официальный источник: OpenAPI Specification

    Минимальный пример OpenAPI

    Что важно в этом примере:

  • paths описывает адреса и методы
  • parameters фиксирует типы и обязательность параметров
  • responses фиксирует возможные статусы и схемы ответов
  • Где хранить спецификацию и как поддерживать актуальность

    Варианты процесса:

  • contract-first: сначала пишете OpenAPI, затем реализуете код
  • code-first: пишете код, а спецификацию генерируете из аннотаций/схем
  • Плюсы contract-first:

  • проще согласовывать контракт с фронтендом и другими командами до реализации
  • меньше риск “случайно” сломать формат
  • Плюсы code-first:

  • меньше ручной работы
  • спецификация ближе к фактической реализации
  • В продакшене часто используют гибрид:

  • contract-first для публичных и критичных API
  • code-first для внутренних сервисов, но с автоматическими проверками
  • !Процесс разработки API от контракта к коду

    Примеры в документации: что обязательно показать

    Минимальный набор для каждого эндпоинта:

  • пример запроса (метод, URL, заголовки)
  • пример тела запроса (если есть)
  • пример успешного ответа
  • примеры ошибок (минимум 400, 401/403 при авторизации, 404 где применимо)
  • Пример секции “как вызвать” (в стиле документации):

    Связь документации с внутренней архитектурой

    Если вы следуете структуре проекта из прошлой статьи, документация становится проще поддерживать:

  • контроллеры описывают HTTP-часть и DTO
  • use case описывает бизнес-результат и бизнес-ошибки
  • слой ошибок превращает бизнес-ошибки в стандартизированные HTTP-ответы
  • Практическое правило:

  • DTO это “контракт”, поэтому изменения в DTO почти всегда требуют изменений в документации
  • доменная модель может развиваться, не ломая контракт, если вы не протекаете доменом наружу
  • Минимальный чеклист REST API перед продакшеном

  • URL и методы соответствуют смыслу ресурсов
  • статусы HTTP выбраны корректно и единообразно
  • формат ошибок единый
  • пагинация и фильтры предсказуемы и ограничены
  • есть стратегия версионирования
  • документация в OpenAPI актуальна и лежит рядом с кодом
  • Следующий шаг в реальной разработке после REST и документации это внедрение аутентификации и авторизации, а также тестирование контракта и наблюдаемость, чтобы API было не только “красивым”, но и надёжным в эксплуатации.

    4. Базы данных: SQL/NoSQL, моделирование и миграции

    Базы данных: SQL/NoSQL, моделирование и миграции

    Как базы данных продолжают темы HTTP, REST и структуры проекта

    В прошлых статьях вы построили основу backend-разработки:

  • HTTP задаёт транспорт и правила общения
  • REST фиксирует внешний контракт API
  • структура проекта и паттерны (контроллеры, use case, репозитории) помогают держать код поддерживаемым
  • База данных добавляет следующую обязательную часть продакшена: надёжное хранение и извлечение данных. Именно на уровне базы данных обычно проявляются ключевые требования к системе: целостность, производительность, масштабирование, возможность безопасно выкатывать изменения.

    !Где база данных находится в архитектуре backend-приложения

    Что такое база данных в практическом смысле

    База данных (БД) это система, которая:

  • хранит данные между перезапусками приложения
  • даёт язык запросов или API для чтения и записи
  • обеспечивает механизмы целостности (например, запретить дублирующийся email)
  • поддерживает конкурентный доступ (много пользователей одновременно)
  • Для backend-разработчика база данных это не только “где лежат данные”, но и набор гарантий, ограничений и компромиссов.

    SQL: реляционные базы данных

    Реляционная модель простыми словами

    В SQL-БД данные обычно представлены как:

  • таблицы
  • строки (записи)
  • столбцы (поля)
  • Ключевая идея реляционных БД: вы явно описываете схему данных и связи между сущностями.

    Популярные SQL-БД:

  • PostgreSQL Documentation
  • MySQL Documentation
  • Первичный ключ и внешние ключи

  • Первичный ключ (primary key) однозначно идентифицирует строку в таблице (например, users.id).
  • Внешний ключ (foreign key) связывает одну таблицу с другой и помогает сохранять целостность (например, orders.user_id должен ссылаться на существующего пользователя).
  • Эти ограничения полезны тем, что ловят ошибки на уровне данных, даже если баг попал в код.

    Транзакции и ACID

    Транзакция это группа операций, которая выполняется как единое целое.

    Реляционные БД обычно дают набор гарантий ACID:

  • Atomicity: либо выполнится всё, либо не выполнится ничего
  • Consistency: данные не переходят в запрещённое состояние (с учётом правил и ограничений)
  • Isolation: параллельные операции не “ломают” друг друга
  • Durability: после подтверждения (commit) данные не потеряются при сбое
  • Практический пример, где транзакция обязательна: создание заказа и списание товара со склада. Если заказ создали, но склад не обновили из-за ошибки, система будет некорректной.

    Дополнительное чтение по транзакциям в PostgreSQL:

  • PostgreSQL: Transactions
  • Индексы: почему запросы ускоряются и чем вы платите

    Индекс это дополнительная структура данных, которая помогает быстрее находить строки по условию.

    Типичный эффект:

  • чтение по индексируемым полям ускоряется
  • записи и обновления могут замедлиться, потому что индекс тоже нужно поддерживать
  • растёт потребление диска
  • Главная практическая мысль: индекс создаётся не “на всякий случай”, а под конкретные запросы.

    Ограничения и уникальность

    SQL-БД позволяют задавать правила на уровне схемы:

  • NOT NULL запрещает отсутствующее значение
  • UNIQUE запрещает дубли (например, один email на одного пользователя)
  • CHECK ограничивает допустимые значения
  • Такие ограничения дополняют валидацию на уровне HTTP и use case: даже если вы забыли проверку в коде, база данных может защитить целостность.

    NoSQL: нереляционные базы данных

    NoSQL это не “против SQL”, а набор подходов, где вы обычно жертвуете частью удобств реляционной модели ради других преимуществ: гибкой структуры, горизонтального масштабирования, высокой скорости записи, специфичных моделей данных.

    Важно: NoSQL решения очень разные по устройству, поэтому выбирать их нужно по типу данных и запросов.

    Документные базы данных

    Данные хранятся документами (часто JSON-подобного вида), структура может эволюционировать легче, чем фиксированная схема таблиц.

  • MongoDB Manual
  • Типичный кейс: сущности со сложной вложенной структурой, где удобно хранить объект целиком и редко нужны сложные join.

    Key-value хранилища

    Работают как “словарь” ключ -> значение.

  • Redis Documentation
  • Типичный кейс: кэш, сессии, счётчики, rate limiting.

    Wide-column базы данных

    Оптимизированы под большие объёмы данных и масштабирование по узлам кластера.

  • Apache Cassandra Documentation
  • Типичный кейс: события, метрики, большие потоки записи, запросы по заранее продуманным ключам.

    Графовые базы данных

    Оптимизированы под запросы связей и обход графа.

  • Neo4j Documentation
  • Типичный кейс: социальные связи, рекомендации, “друзья друзей”, зависимости.

    Согласованность и компромиссы

    В распределённых системах часто возникает компромисс между:

  • согласованностью: все клиенты видят одно и то же в один и тот же момент
  • доступностью: система отвечает даже при проблемах с частью узлов
  • устойчивостью к разделению сети: система продолжает работать, даже если сеть “разрезало” на части
  • Эта идея известна как CAP-теорема.

    Практический вывод для backend-разработчика:

  • SQL-БД чаще дают сильную согласованность и удобные транзакции в рамках одного кластера.
  • Некоторые NoSQL системы проще масштабируются горизонтально, но могут дать eventual consistency (согласованность “со временем”).
  • Выбор хранилища напрямую зависит от требований продукта: что важнее, строгая целостность или доступность под высокой нагрузкой.
  • Моделирование данных: как превратить требования в структуру хранения

    Начинать нужно с предметной области

    Моделирование данных начинается не с таблиц, а с ответов на вопросы:

  • какие сущности существуют (пользователь, заказ, товар)
  • какие у них атрибуты (email, статус, цена)
  • какие между ними связи (у пользователя много заказов)
  • какие операции и запросы будут самыми частыми
  • Если вы проектируете только “как красиво”, но не учитываете запросы, вы получите либо медленную систему, либо сложную схему, которую трудно развивать.

    Связи между сущностями

    Самые распространённые типы связей:

  • один к одному: пользователь и профиль (если профиль выделен отдельно)
  • один ко многим: пользователь и заказы
  • многие ко многим: товары и категории
  • !Пример модели данных и связей для типового backend-сервиса

    Нормализация и денормализация

    Нормализация это подход, при котором данные раскладываются по таблицам так, чтобы:

  • уменьшить дублирование
  • избежать аномалий обновления (когда одно и то же значение нужно менять в нескольких местах)
  • Пример проблемы без нормализации: email пользователя продублирован в таблице заказов. Если пользователь сменил email, нужно обновить много строк, и легко получить несогласованность.

    Денормализация это осознанное добавление дублирования ради производительности чтения.

    Правило продакшена: денормализация делается тогда, когда вы понимаете, какой запрос узкий горлышко, и чем вы платите за ускорение (сложность обновлений и риск рассинхронизации).

    Проектирование под запросы

    Backend редко работает с “всеми данными”. Обычно есть несколько ключевых запросов:

  • получить пользователя по email
  • получить список заказов пользователя с пагинацией
  • получить карточку заказа с позициями
  • Из этого следуют практические решения:

  • Индексы ставятся под реальные фильтры и сортировки.
  • Связи проектируются так, чтобы типовые сценарии работали без “каскада” запросов.
  • В API для списков почти всегда нужна пагинация, а в БД для неё нужен понятный порядок (например, сортировка по времени создания).
  • Миграции: как безопасно менять схему базы данных

    Что такое миграции

    Миграция это версионированное изменение схемы базы данных: создать таблицу, добавить колонку, создать индекс, изменить ограничение.

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

    Почему нельзя “просто поправить базу руками”

    Ручные изменения почти всегда приводят к проблемам:

  • невозможно повторить окружение (у разработчиков и на staging разные схемы)
  • непонятно, какая версия схемы сейчас в продакшене
  • откаты становятся рискованными
  • CI не может поднять базу “с нуля” на чистой схеме
  • Поэтому миграции обычно хранятся в репозитории рядом с кодом.

    Типовой жизненный цикл миграции

  • Разработчик создаёт миграцию как отдельный файл.
  • Миграция применяется локально и в тестах.
  • CI поднимает базу с нуля и прогоняет все миграции.
  • На деплое миграция применяется перед запуском новой версии приложения или как отдельный шаг пайплайна.
  • Backward compatible изменения и подход expand/contract

    Самая частая причина инцидентов: новая версия приложения ожидает новую схему, а часть серверов ещё работает со старой (или наоборот).

    Чтобы выкатывать изменения без простоя, используется подход expand/contract:

  • Expand: добавьте новую колонку или таблицу так, чтобы старая версия приложения продолжала работать.
  • Dual-write или совместимость: приложение на время пишет и читает так, чтобы поддерживать оба формата.
  • Contract: когда старый код полностью выключен, удалите старые поля и ограничения.
  • !Как менять схему БД без простоя и поломки старых версий приложения

    Примеры SQL-миграций

    Создание таблиц и ограничений:

    Добавление поля (шаг expand):

    Важно: добавление NOT NULL и “тяжёлых” индексов на больших таблицах может быть небезопасным без подготовки, потому что операция может занять много времени и блокировать запросы. В продакшене такие изменения планируют отдельно.

    Инструменты для миграций

    Инструмент зависит от языка и стека, но смысл одинаков: применять миграции по порядку и хранить версию схемы.

    Примеры популярных инструментов:

  • Flyway Documentation
  • Liquibase Documentation
  • Alembic Documentation
  • Django Migrations
  • Prisma Migrate
  • golang-migrate/migrate
  • Как слой данных связан с архитектурой проекта

    С учётом структуры из прошлой статьи полезно держать следующие границы:

  • HTTP-слой не строит SQL и не знает деталей схемы
  • use case оперирует бизнес-сущностями и правилами
  • репозиторий отвечает за запросы к БД и преобразование данных в доменную модель
  • Это позволяет:

  • тестировать бизнес-логику без реальной базы
  • менять схему и оптимизировать запросы без переписывания контроллеров
  • поддерживать несколько хранилищ (например, SQL для основной модели и Redis для кэша)
  • Минимальный чеклист перед продакшеном

  • Схема данных отражает реальные запросы и сценарии.
  • Есть ограничения целостности: ключи, уникальность, обязательность.
  • Для горячих запросов предусмотрены индексы.
  • Есть миграции в репозитории и автоматическое применение в CI.
  • Изменения схемы планируются как backward compatible, если деплой не атомарный.
  • Кэш (например, Redis) используется как ускорение, а не как единственный источник истины, если данные критичны.
  • 5. Аутентификация, авторизация и безопасность

    Аутентификация, авторизация и безопасность

    Как эта тема связана с HTTP, REST и архитектурой проекта

    В предыдущих темах вы разобрали:

  • HTTP как транспорт (методы, статусы, заголовки)
  • REST как контракт (ресурсы, коды ответов, формат ошибок)
  • архитектурные слои (контроллеры, use case, репозитории)
  • базы данных и миграции (целостность и безопасные изменения)
  • Безопасность «склеивает» всё это в продакшене:

  • аутентификация и авторизация определяют, кто делает запрос и что ему можно
  • защита данных опирается на HTTPS, корректную работу с заголовками, безопасное хранение секретов
  • большинство уязвимостей возникает на границах слоёв: HTTP-ввод, бизнес-правила, доступ к данным
  • !Диаграмма показывает, где в запросе обычно выполняются аутентификация и авторизация

    Термины: что именно мы защищаем

    Аутентификация

    Аутентификация отвечает на вопрос: кто вы? Обычно это проверка учётных данных.

    Примеры:

  • email и пароль
  • токен доступа (Bearer token)
  • API key
  • клиентский сертификат (mTLS)
  • Авторизация

    Авторизация отвечает на вопрос: что вам можно? Это проверка прав на конкретное действие и ресурс.

    Примеры:

  • «пользователь может читать только свои заказы»
  • «роль admin может удалять пользователей»
  • «токен со scope orders:write может создавать заказ, но не возвращать финансовые отчёты»
  • Principal, роли и права

  • Principal: субъект, от имени которого выполняется запрос (пользователь, сервис, интеграция).
  • Role (роль): группировка прав (например, admin, manager, customer).
  • Permission (право): конкретное действие (например, orders.read, orders.cancel).
  • Аутентификация в HTTP API: основные подходы

    Сессии и cookies

    Схема сессий обычно выглядит так:

  • клиент логинится (например, POST /sessions)
  • сервер создаёт запись сессии на своей стороне и возвращает cookie с идентификатором
  • в последующих запросах браузер автоматически отправляет cookie
  • Ключевые понятия:

  • cookie передаётся через заголовок Cookie, а устанавливается через Set-Cookie
  • атрибуты cookie важны для безопасности (например, HttpOnly, Secure, SameSite)
  • Полезные источники:

  • MDN: Set-Cookie
  • MDN: Cookies
  • Когда это удобно:

  • классические веб-приложения в браузере
  • Риски и особенности:

  • требуется защита от CSRF (если аутентификация опирается на cookie)
  • нужно хранить состояние сессии (или использовать распределённое хранилище)
  • Bearer token и заголовок Authorization

    Для API часто используют токены доступа, которые клиент явно передаёт в заголовке:

    Источники:

  • MDN: Authorization header
  • RFC 6750: Bearer Token Usage
  • Плюсы:

  • удобно для мобильных приложений и сервер-сервер интеграций
  • меньше зависимость от браузерной автоматической отправки cookie
  • Минусы:

  • токен нужно защищать на клиенте (утечка токена равна компрометации доступа)
  • требуется продуманная стратегия сроков жизни и обновления
  • JWT как формат токена

    JWT (JSON Web Token) часто используют как самодостаточный токен: он содержит набор утверждений (claims) и подпись.

    Ключевые моменты:

  • JWT обычно подписывают (JWS), чтобы сервер мог проверять целостность
  • JWT не обязательно шифруют; не стоит класть в него секретные данные
  • JWT удобен для горизонтального масштабирования, потому что не требует хранения сессии на сервере
  • Главная эксплуатационная особенность:

  • если токен «самодостаточный», то его сложнее отозвать до истечения срока действия, поэтому срок жизни access token обычно делают коротким, а обновление делают через refresh token
  • Спецификация:

  • RFC 7519: JSON Web Token (JWT)
  • API keys

    API key часто применяют для простых интеграций.

    Практические правила:

  • ключ должен передаваться по HTTPS
  • ключ должен иметь ограничения (например, права, квоты, привязку к проекту)
  • ключ должен ротироваться
  • Минус:

  • API key часто идентифицирует интеграцию, но не всегда хорошо решает задачу «пользовательских» прав и делегирования
  • OAuth 2.0 и OpenID Connect

    Если у вас сценарии типа «войти через провайдера» или «делегировать доступ внешнему приложению», применяют OAuth 2.0.

  • OAuth 2.0 решает задачу делегирования доступа
  • OpenID Connect (OIDC) добавляет слой идентификации поверх OAuth 2.0
  • Источники:

  • RFC 6749: The OAuth 2.0 Authorization Framework
  • OpenID Connect Core 1.0
  • Как выбирать статус-коды и формат ошибок

    Связь с темой HTTP и REST:

  • 401 Unauthorized: аутентификация отсутствует или невалидна (например, нет токена, токен просрочен)
  • 403 Forbidden: аутентификация есть, но прав недостаточно
  • Хорошая практика для продакшена:

  • не раскрывать лишние детали (например, «пользователь существует, но пароль неверный»)
  • сохранять единый формат ошибок из предыдущих тем, добавляя машинный код
  • Пример:

    Авторизация: модели и типовые ошибки

    RBAC: Role-Based Access Control

    RBAC означает, что права выдаются через роли.

    Пример:

  • admin может удалять пользователей
  • customer может читать и создавать свои заказы
  • Плюсы:

  • проще администрировать
  • Минусы:

  • сложно выразить тонкие правила без разрастания числа ролей
  • Scope-based авторизация

    Scopes обычно используются вместе с OAuth2 и токенами.

    Пример:

  • orders:read
  • orders:write
  • Это удобно для интеграций и сервисных токенов.

    Проверка владения ресурсом

    Самая частая бизнес-ошибка авторизации в API:

  • пользователь аутентифицирован
  • у него есть доступ к эндпоинту
  • но он читает или меняет чужой ресурс
  • Практическое правило:

  • любая операция с ресурсом должна иметь явную проверку «имеет ли principal право на этот конкретный resourceId»
  • Где это должно жить в архитектуре:

  • проверку токена и извлечение principal удобно делать в middleware
  • проверку бизнес-прав (роль, владение ресурсом, статус заказа) чаще правильнее делать в use case, потому что это часть бизнес-логики
  • Защита учётных данных и секретов

    Хранение паролей

    Пароли нельзя хранить в открытом виде и нельзя «просто шифровать» симметричным ключом как замену хешированию.

    Практический стандарт:

  • хранить хеш пароля, полученный алгоритмом, предназначенным для паролей
  • использовать уникальную соль (salt) на каждый пароль
  • Современная рекомендация:

  • Argon2id описан в стандарте
  • Источник:

  • RFC 9106: Argon2 Memory-Hard Function for Password Hashing
  • Секреты приложения

    К секретам относятся:

  • ключи подписи JWT
  • пароли к базе данных
  • API keys внешних сервисов
  • Правила:

  • не хранить секреты в репозитории
  • передавать секреты через переменные окружения или secret-хранилища
  • ротировать секреты
  • ограничивать доступ по принципу минимально необходимых привилегий
  • Типовые угрозы для backend и базовые защиты

    Ниже перечислены практические темы, которые чаще всего встречаются в продакшене.

    Общий ориентир по классам уязвимостей:

  • OWASP Top 10
  • TLS и HTTPS

  • весь трафик с учётными данными должен идти по HTTPS
  • если сервис общается с сервисом внутри инфраструктуры, тоже важно защищать трафик; иногда применяют mTLS
  • SQL injection и работа с данными

    Связь с темой баз данных:

  • опасно собирать SQL строкой из пользовательского ввода
  • безопаснее использовать параметризованные запросы или корректные механизмы ORM
  • Правило:

  • валидация входных данных относится к HTTP-слою и DTO, но защита от инъекций должна быть системной: параметризация на уровне репозитория
  • CSRF

    CSRF актуален, когда аутентификация завязана на cookie, потому что браузер может автоматически отправить cookie на ваш домен.

    Типовые меры:

  • SameSite для cookies
  • CSRF token (отдельный токен, который должен отправляться из вашего UI)
  • Источник по атрибуту SameSite:

  • MDN: SameSite cookies
  • CORS

    CORS это механизм браузера, который ограничивает, какие фронтенды могут вызывать ваш API из контекста страницы.

    Важно понимать границу ответственности:

  • CORS защищает браузер, а не ваш сервер
  • CORS не заменяет авторизацию
  • Источник:

  • MDN: CORS
  • Brute force и rate limiting

    Риски:

  • перебор паролей на /login
  • атаки на чувствительные эндпоинты (например, проверка промокодов)
  • Меры:

  • rate limiting по IP, по аккаунту, по API key
  • задержки и блокировки при подозрительной активности
  • аудит логов и алерты
  • Безопасная обработка ошибок

    Практические правила:

  • не возвращать stack trace клиенту
  • не выдавать детали, упрощающие атаку (например, точные причины отказа при логине)
  • логировать достаточно для расследования (например, requestId, principal, тип ошибки), но не логировать чувствительные данные (пароли, полные токены)
  • Минимальный продакшен-чеклист

  • везде HTTPS; нет токенов и паролей в логах
  • корректные статусы: 401 для проблем аутентификации, 403 для недостатка прав
  • аутентификация вынесена в middleware, бизнес-правила авторизации проверяются в use case
  • пароли хешируются алгоритмом для паролей, с уникальной солью
  • секреты хранятся вне репозитория и ротируются
  • есть защита от CSRF при cookie-based аутентификации
  • входные данные валидируются, доступ к БД использует параметризацию
  • есть rate limiting на чувствительных эндпоинтах
  • В следующих темах курса (когда вы перейдёте к продакшен-практикам вроде логирования, метрик, тестирования и деплоя) безопасность нужно будет «закрепить» процессом: проверками в CI, безопасной конфигурацией окружений и регулярным пересмотром угроз.

    6. Тестирование, логирование и наблюдаемость

    Тестирование, логирование и наблюдаемость

    Как эта тема связана с предыдущими

    В предыдущих статьях вы построили основу backend-разработки:

  • HTTP и REST как внешний контракт (методы, статусы, форматы ошибок, документация)
  • архитектурные слои (контроллеры, use case, репозитории)
  • базы данных и миграции (целостность и безопасные изменения схемы)
  • аутентификация, авторизация и безопасность (кто вы и что вам можно)
  • Тестирование, логирование и наблюдаемость (observability) отвечают на продакшен-вопросы:

  • мы уверены, что изменения не сломали поведение?
  • если в продакшене что-то пошло не так, мы можем быстро понять, что именно и где?
  • мы можем увидеть деградацию ещё до того, как пользователи начнут жаловаться?
  • !Как запрос проходит через слои приложения и какие сигналы собирает наблюдаемость

    Тестирование: зачем оно нужно именно backend-разработчику

    Тесты помогают одновременно решать три задачи:

  • предотвращать регрессии (когда «вчера работало», а сегодня сломали)
  • ускорять изменения (меньше ручных проверок)
  • фиксировать контракт и правила (какое поведение считается правильным)
  • Важно: тестирование не заменяет правильную архитектуру, но архитектура сильно влияет на тестируемость.

    Базовые термины

  • Unit-тест проверяет маленький фрагмент логики (обычно функция/класс) изолированно.
  • Интеграционный тест проверяет взаимодействие частей системы (например, репозиторий + реальная база данных).
  • End-to-end (E2E) тест проверяет сценарий целиком через внешний интерфейс (например, HTTP API).
  • Mock это «подмена» зависимости в тесте (например, фейковый репозиторий вместо БД).
  • Stub это упрощённая заглушка, которая возвращает заранее заданные ответы.
  • Пирамида тестов как практический ориентир

    Обычно выгодно держать больше дешёвых тестов и меньше дорогих:

  • unit-тесты быстрые и массовые
  • интеграционные тесты медленнее, но ловят ошибки границ (SQL, миграции, сериализация)
  • E2E-тесты самые дорогие и хрупкие, но дают максимальную уверенность
  • !Почему тесты разных уровней дополняют друг друга

    Что тестировать в слоях архитектуры

    В терминах структуры из прошлых тем:

  • Use case / сервисы: главный кандидат для unit-тестов, потому что там бизнес-правила.
  • Репозитории: часто требуют интеграционных тестов с реальной БД, чтобы проверить SQL, индексы, транзакции.
  • HTTP-слой: тестируют сериализацию/валидацию/статусы и формат ошибок.
  • Практическое правило: чем ближе код к доменной логике, тем проще изолировать тест, тем он стабильнее.

    Примеры: что считать хорошими тестами

    #### Unit-тест бизнес-правила

    Проверяем правило в use case, не поднимая HTTP и БД.

    Это тестирует правило, а не транспорт.

    #### Интеграционный тест репозитория

    Проверяем, что:

  • миграции применяются
  • запросы действительно работают
  • уникальные ограничения ловят ошибки
  • Пример ожидаемого эффекта:

    #### E2E-тест критического сценария

    Обычно оставляют только «сквозные» сценарии, которые важнее всего для продукта:

  • регистрация/логин
  • создание заказа
  • оплата (или имитация оплаты в тестовом контуре)
  • Контрактные тесты и OpenAPI

    Из темы про документацию важно следующее: OpenAPI это контракт. Его можно использовать в тестировании:

  • проверять, что ответы соответствуют схеме
  • проверять, что коды статусов и структура ошибок не «поплыли»
  • Подходы:

  • contract-first: контракт утверждён, тесты проверяют реализацию
  • code-first: контракт генерируется из кода, тесты проверяют совместимость версий
  • Спецификация: OpenAPI Specification

    Как тесты связываются с безопасностью

    В статье про безопасность вы разбирали 401 и 403. Это нужно тестировать системно:

  • отсутствие токена даёт 401
  • недостаток прав даёт 403
  • доступ к чужому ресурсу запрещён (часто тоже 403, иногда маскируют как 404 по политике безопасности)
  • Тестовые данные, изоляция и миграции

    Типовые проблемы тестов вокруг БД:

  • тесты мешают друг другу данными
  • база имеет «грязное» состояние
  • схема отличается от продакшена
  • Практики, которые обычно помогают:

  • применять миграции в тестовом окружении так же, как в CI/проде
  • изолировать данные (отдельная БД на прогон, отдельная схема, транзакции с rollback)
  • создавать тестовые данные фабриками/фикстурами, чтобы тесты были читаемыми
  • Логирование: как писать логи, которые помогают в инциденте

    Логи отвечают на вопрос: что происходило? Но ценность логов зависит от того, насколько они:

  • структурированы
  • коррелируются между сервисами
  • не содержат лишнего и чувствительного
  • Уровни логов

    | Уровень | Когда использовать | Пример | |---|---|---| | DEBUG | диагностика на стенде/в разработке | детали запроса к внешнему сервису | | INFO | значимые события нормальной работы | «заказ создан» | | WARN | проблема, но сервис продолжает работать | «таймаут внешнего API, будет ретрай» | | ERROR | операция не выполнена/ошибка запроса | «не удалось сохранить заказ» |

    Не все команды используют одинаковые уровни, но важно иметь единый подход в проекте.

    Структурированные логи

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

    Пример (упрощённо):

    Корреляция: requestId и traceId

    Чтобы связать события одного запроса:

  • генерируют requestId на входе (или принимают из X-Request-Id)
  • прокидывают его через все логи в рамках обработки
  • Если у вас несколько сервисов и распределённые вызовы, добавляют трассировку (обычно с traceId).

    Что нельзя логировать

    Связь с темой безопасности:

  • пароли
  • refresh tokens
  • полные access tokens
  • секреты и ключи
  • персональные данные без необходимости
  • Если нужно логировать идентификатор пользователя, логируйте userId, а не весь профиль.

    Логи ошибок: единый формат и смысл

    Удобная практика: различать типы ошибок так, чтобы по логам было ясно:

  • это ошибка клиента (4xx) или ошибка сервера (5xx)
  • это ожидаемая бизнес-ошибка или баг
  • При этом клиенту нельзя отдавать stack trace, а в логах на сервере он полезен.

    Наблюдаемость: метрики, трассировки и алерты

    Наблюдаемость (observability) это способность понять внутреннее состояние системы по её внешним сигналам.

    Классический практический набор сигналов:

  • метрики: числа во времени (latency, RPS, error rate)
  • логи: события с контекстом
  • трейсы (traces): путь запроса через компоненты и время каждого шага
  • Введение и практики доступны в книге: Site Reliability Engineering

    Метрики: что собирать в первую очередь

    Для HTTP API обычно начинают с так называемых «четырёх золотых сигналов»:

  • latency (время ответа)
  • traffic (нагрузка)
  • errors (доля ошибок)
  • saturation (насколько исчерпываются ресурсы)
  • Примеры конкретных метрик:

  • http_requests_total с разбиением по маршруту, методу, статусу
  • http_request_duration_seconds (гистограмма времени)
  • db_query_duration_seconds по ключевым запросам
  • использование пула соединений к БД
  • Инструмент сбора метрик часто строят вокруг Prometheus: Prometheus Documentation

    Визуализация и дашборды часто делаются в Grafana: Grafana Documentation

    Трассировки: как найти «где тормозит»

    Трейс показывает, сколько времени заняли этапы:

  • middleware
  • use case
  • вызовы в БД
  • вызовы во внешние сервисы
  • Обычно это решает проблему: сервис медленный, но непонятно почему.

    Стандарт де-факто для телеметрии: OpenTelemetry Documentation

    Для просмотра трейсинга часто используют Jaeger: Jaeger Documentation

    Алерты: что считать проблемой

    Алерт это правило, которое сообщает, что система вышла за границы нормы. Хорошие алерты:

  • привязаны к пользовательскому эффекту (например, рост 5xx и p95 latency)
  • имеют понятное действие («что делать дежурному»)
  • не шумят без причины
  • Частый анти-паттерн: алертить «CPU 80%» без контекста пользовательского воздействия.

    SLI, SLO и SLA простыми словами

    Эти термины помогают формализовать качество:

  • SLI: измеряемый индикатор (например, доля успешных запросов)
  • SLO: целевое значение SLI (например, 99.9% успешных запросов за 30 дней)
  • SLA: договорное обязательство перед клиентом (обычно с последствиями)
  • Даже если вы не внедряете SLO формально, полезно мыслить порогами и окнами времени.

    Как собрать это в единый продакшен-процесс

    Минимальный практический пайплайн качества

  • Unit-тесты запускаются на каждый коммит.
  • Интеграционные тесты запускаются в CI с поднятием зависимостей (например, тестовая БД).
  • Контрактные проверки не дают незаметно сломать API.
  • В продакшене включены метрики, логи и трассировки.
  • Есть дашборды и алерты для ключевых пользовательских сценариев.
  • Что часто ломают новички

  • смешивают бизнес-логику с HTTP-слоем и потом не могут нормально unit-тестировать
  • пишут «много логов», но без requestId, и расследование превращается в ручной поиск
  • логируют секреты и персональные данные
  • собирают метрики, но не используют их в алертах и решениях
  • Минимальный чеклист перед продакшеном

  • Основная бизнес-логика покрыта unit-тестами.
  • Репозитории имеют интеграционные тесты, миграции применяются автоматически.
  • Есть хотя бы несколько E2E-тестов для критических сценариев.
  • Логи структурированы, везде есть requestId (и при необходимости traceId).
  • Не логируются пароли, токены, секреты и лишние персональные данные.
  • Есть базовые метрики (RPS, latency, error rate) и дашборды.
  • Есть алерты на деградацию пользовательского опыта.
  • Эта тема завершает «переход к продакшену»: вы не просто пишете API, но умеете доказывать его корректность тестами и поддерживать его в эксплуатации через наблюдаемость.

    7. Производительность, масштабирование и деплой

    Производительность, масштабирование и деплой

    Как эта тема связывает всё в «продакшен»

    В предыдущих статьях вы построили базу backend-разработки:

  • HTTP и REST задали транспорт и контракт.
  • Архитектурные слои помогли держать код тестируемым и поддерживаемым.
  • Базы данных и миграции дали целостность и управляемые изменения схемы.
  • Аутентификация и безопасность защитили доступ.
  • Тестирование, логирование и наблюдаемость дали уверенность и диагностику.
  • Теперь добавляем три продакшен-измерения:

  • Производительность: система быстро отвечает и эффективно использует ресурсы.
  • Масштабирование: система выдерживает рост нагрузки без переписывания с нуля.
  • Деплой: изменения выкатываются предсказуемо и безопасно.
  • !Общее место производительности и масштабирования: где появляются узкие места и как их разгружать

    Производительность: что измеряем и что оптимизируем

    Термины без «магии»

  • Latency (задержка): сколько времени проходит от запроса до ответа.
  • Throughput (пропускная способность): сколько запросов в секунду система обрабатывает.
  • RPS: частный случай throughput для HTTP.
  • p95/p99 latency: 95-й/99-й процентиль времени ответа, показывает «хвосты».
  • Bottleneck (узкое место): компонент, который ограничивает скорость всей системы.
  • Практическое правило: оптимизация без измерений часто ухудшает систему.

    Базовый цикл работы с производительностью

  • Определите целевые метрики на уровне API: RPS, p95 latency, error rate.
  • Снимите измерения в условиях, похожих на реальность.
  • Найдите узкое место по метрикам и трейсингу.
  • Сделайте одно изменение.
  • Снова измерьте и сравните.
  • Связь с наблюдаемостью из прошлой темы: чаще всего узкое место видно через метрики и трассировки в OpenTelemetry.

  • Документация OpenTelemetry
  • Документация Prometheus
  • Где обычно «тормозит» backend

    База данных как самый частый источник задержек

    Типовые причины:

  • медленные запросы из-за отсутствия индексов
  • N+1 запрос (много мелких запросов вместо одного)
  • чтение лишних колонок и строк
  • блокировки и долгие транзакции
  • слишком маленький или слишком большой пул соединений
  • Практики:

  • Добавляйте индексы под реальные фильтры и сортировки (из темы про моделирование).
  • Следите за временем запросов и количеством запросов на один HTTP-запрос.
  • Держите транзакции короткими: «сделал работу и commit».
  • Полезно знать инструменты диагностики:

  • PostgreSQL: EXPLAIN
  • PostgreSQL: Monitoring Database Activity
  • Сериализация, валидация и «лишняя работа» в HTTP-слое

    Частые проблемы:

  • большие JSON-ответы без пагинации
  • тяжёлая валидация на каждый запрос без нужды
  • синхронные вызовы внешних сервисов в критическом пути
  • Практики:

  • Для списков всегда используйте пагинацию и лимиты (как в теме про REST).
  • Возвращайте только нужные поля (DTO помогают держать контракт под контролем).
  • Вынесите тяжёлые операции в асинхронную обработку.
  • Внешние зависимости

    Если ваш сервис вызывает другие сервисы:

  • задержки и таймауты внешней системы станут вашими задержками
  • без ограничений параллелизма можно «положить» и себя, и зависимость
  • Базовые меры устойчивости:

  • разумные таймауты
  • ретраи только там, где безопасно по смыслу операции
  • circuit breaker (временное «отключение» зависимостей при деградации)
  • Кэширование: ускоряем чтение, но не ломаем корректность

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

    Виды кэширования, которые встречаются в backend

  • HTTP-кэширование на уровне CDN/reverse proxy для публичных GET-ответов.
  • Кэш приложения (in-memory) для очень горячих данных, но с осторожностью.
  • Распределённый кэш (часто Redis) для общих между инстансами данных.
  • Документация Redis
  • Две базовые стратегии кэша

  • Cache-aside: приложение сначала проверяет кэш, при промахе читает из БД и кладёт в кэш.
  • Write-through/Write-behind: запись проходит через кэш, а БД обновляется синхронно или асинхронно.
  • Для большинства CRUD API безопасный стартовый вариант — cache-aside.

    Инвалидация и TTL

    Практические правила:

  • Ставьте TTL (время жизни), чтобы кэш сам «устаревал».
  • На изменениях данных инвалидируйте кэш по ключу, если возможна некорректность.
  • Не кэшируйте персональные данные без чёткого понимания модели доступа.
  • Асинхронность и очереди: разгружаем критический путь

    Если операция не обязана завершаться в рамках HTTP-ответа, её выгодно делать асинхронно.

    Типовые кандидаты:

  • отправка email/SMS
  • генерация отчётов
  • обработка изображений
  • интеграции с внешними системами, где допустима задержка
  • Что меняется в API:

  • вместо «сделать сейчас» вы часто отвечаете 202 Accepted
  • даёте клиенту идентификатор задачи и эндпоинт статуса
  • Очередь помогает:

  • выровнять пики нагрузки
  • ретраить задачи отдельно от пользовательских запросов
  • ограничивать параллелизм обработки
  • Масштабирование: как расти без переписывания

    Вертикальное и горизонтальное

  • Вертикальное масштабирование: больше CPU/RAM одной машине.
  • Горизонтальное масштабирование: больше экземпляров сервиса.
  • На практике горизонтальный рост требует архитектурного условия: сервис должен быть близок к stateless.

    Stateless на практике

    Сервис проще масштабировать, если:

  • состояние пользователя не хранится в памяти процесса
  • сессии лежат в общем хранилище (например, Redis) или используются токены
  • файлы не пишутся на локальный диск как на единственное место хранения
  • Это напрямую связано с темой аутентификации:

  • JWT и короткоживущие токены часто упрощают горизонтальное масштабирование
  • cookie-based сессии тоже масштабируются, если сессии лежат в общем хранилище
  • Балансировка нагрузки

    Когда у вас несколько инстансов, нужен компонент, который распределяет запросы:

  • reverse proxy / ingress
  • load balancer облака
  • Задачи балансировщика:

  • распределять трафик
  • терминировать TLS (часто)
  • применять rate limiting
  • маршрутизировать по путям/хостам
  • Пример документации reverse proxy:

  • Документация NGINX
  • Автоскейлинг и лимиты ресурсов

    В контейнерной инфраструктуре вы обычно задаёте:

  • requests: сколько ресурсов нужно сервису
  • limits: потолок потребления
  • И включаете автоскейлинг по метрикам:

  • CPU
  • RPS
  • latency
  • кастомные метрики
  • Деплой: как выкатывать изменения безопасно

    Деплой — это не «залить код на сервер», а повторяемый процесс доставки версии приложения в окружение.

    Минимальные требования к продакшен-деплою

  • одинаковая сборка для всех окружений
  • конфигурация вынесена из кода (принципы Twelve-Factor App)
  • миграции применяются управляемо
  • есть health checks
  • есть graceful shutdown
  • The Twelve-Factor App
  • Контейнеризация как стандарт упаковки

    Частая модель в индустрии:

  • приложение собирается в Docker-образ
  • образ разворачивается одинаково в staging и production
  • Документация Docker
  • Оркестрация и Kubernetes

    Когда сервисов и инстансов становится много, появляется оркестратор. Самый распространённый — Kubernetes.

    Что обычно даёт Kubernetes в контексте деплоя:

  • rolling update (постепенная замена версий)
  • self-healing (перезапуск упавших контейнеров)
  • сервис-дискавери
  • управление конфигурацией и секретами
  • горизонтальный автоскейлинг
  • Документация Kubernetes
  • Стратегии деплоя

    Выбор стратегии зависит от критичности и требуемого риска.

    | Стратегия | Суть | Плюсы | Минусы | |---|---|---|---| | Rolling | постепенно заменяем инстансы на новую версию | просто, обычно по умолчанию | риск, если новая версия ломает совместимость | | Blue/Green | две среды, переключение трафика | быстрый откат | дороже по ресурсам | | Canary | небольшой процент трафика на новую версию | минимизация риска | сложнее в настройке и мониторинге |

    !Сравнение стратегий деплоя по распределению трафика во времени

    Совместимость схемы БД и деплой без простоя

    Это продолжение темы миграций и подхода expand/contract.

    Ключевая проблема: во время деплоя некоторое время одновременно работают две версии приложения.

    Практический безопасный порядок:

  • Сделайте миграцию типа expand (добавить колонку/таблицу без удаления старого).
  • Выложите код, который умеет работать и со старым, и с новым форматом.
  • Дождитесь, пока все инстансы обновятся.
  • Удалите старое (contract) отдельной миграцией.
  • Если деплой атомарный (вся система обновляется одновременно), рисков меньше, но в реальности атомарность часто недостижима.

    Health checks и готовность принимать трафик

    Чтобы балансировщик не отправлял запросы в «полуживой» инстанс, используют проверки:

  • liveness: процесс жив?
  • readiness: инстанс готов обслуживать запросы?
  • Readiness обычно должен учитывать критичные зависимости (например, подключение к БД) и прогретые ресурсы.

    Graceful shutdown

    При остановке инстанса важно:

  • перестать принимать новые запросы
  • завершить текущие
  • закрыть соединения с БД
  • Иначе вы получите оборванные запросы и ошибки на ровном месте.

    CI/CD как процесс

    Типовой пайплайн:

  • Запуск unit-тестов.
  • Интеграционные тесты с поднятием БД.
  • Сборка артефакта (обычно Docker-образ).
  • Публикация образа в registry.
  • Деплой в staging.
  • Автоматические проверки (smoke-тесты, базовые E2E).
  • Деплой в production выбранной стратегией.
  • Связь с темой наблюдаемости:

  • для canary/rolling вам нужны метрики и алерты, чтобы остановить выкладку при деградации
  • релиз без мониторинга превращается в «надежду»
  • Практический чеклист перед выкладкой

  • Есть метрики RPS, error rate, p95 latency по основным эндпоинтам.
  • Есть трейсинг ключевых сценариев через БД и внешние зависимости.
  • Включены таймауты на внешние вызовы и к БД.
  • Кэширование не ломает корректность (TTL и инвалидация продуманы).
  • Деплой учитывает совместимость схемы БД (expand/contract).
  • Настроены readiness/liveness и graceful shutdown.
  • Есть понятная стратегия отката версии.
  • Эта тема завершает «сквозную линию» курса: вы не только проектируете API и пишете код, но и делаете систему быстрой, готовой к росту и безопасной в доставке изменений в production.