Backend-разработка на Spring Boot: MVP, REST API и базы данных

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

1. Основы Spring Boot и создание MVP: структура проекта, конфигурация, слои

Основы Spring Boot и создание MVP: структура проекта, конфигурация, слои

В этом курсе мы будем учиться делать прикладной backend на Spring Boot: быстро поднимать сервис, проектировать REST API и подключать базу данных. Эта статья — стартовая: разберём, как устроен типичный проект Spring Boot, где хранится конфигурация и как правильно разделять код по слоям.

Что такое Spring Boot и почему он подходит для MVP

Spring Boot — это надстройка над экосистемой Spring, которая помогает быстро запустить приложение со стандартными настройками.

MVP (Minimum Viable Product) — это минимально жизнеспособная версия продукта: есть только базовые функции, но они работают и их можно показать пользователям.

Spring Boot особенно удобен для MVP, потому что:

  • даёт готовый веб-сервер и автоконфигурацию
  • легко подключает зависимости (web, базы данных, безопасность)
  • позволяет быстро сделать REST API
  • Полезные официальные источники:

  • Spring Boot Reference Documentation
  • Building a RESTful Web Service
  • Accessing Data with JPA
  • Как создать проект Spring Boot

    Самый простой способ — через генератор:

  • Spring Initializr
  • Для MVP обычно достаточно выбрать:

  • Spring Web (REST API)
  • Spring Data JPA (работа с базой через ORM)
  • H2 Database (временная in-memory база для разработки) или PostgreSQL Driver (для реальной базы)
  • Validation (валидация входных данных)
  • После генерации у вас будет проект, который уже запускается.

    Структура типичного проекта

    В Spring Boot принято держать код в одном корневом пакете (например, com.example.demo), а ниже — разносить по смыслу.

    Рекомендуемая структура для небольшого MVP:

    Что это значит:

  • DemoApplication.java — точка входа (отсюда стартует приложение)
  • controller/ — слой API (принимает HTTP-запросы)
  • service/ — бизнес-логика (правила приложения)
  • repository/ — доступ к данным (запросы к базе)
  • domain/entity/ — сущности, которые хранятся в базе
  • domain/dto/ — объекты для обмена данными через API
  • config/ — явные настройки (если автоконфигурации недостаточно)
  • resources/application.yml — конфигурация приложения
  • Главный класс приложения

    Пример типового файла:

    Здесь важно понимать:

  • @SpringBootApplication включает автоконфигурацию и сканирование компонентов
  • Spring сам найдёт классы в этом пакете и подпакетах (контроллеры, сервисы, репозитории)
  • Конфигурация: application.yml и профили

    Конфигурация — это параметры приложения: порт, подключение к базе, уровень логов и так далее.

    Чаще всего используют application.yml (или application.properties). Пример минимального application.yml:

    Подключение базы данных (пример)

    Для MVP удобно иметь разные настройки для разработки и для продакшена. Для этого используют профили.

    Пример application-dev.yml (локальная разработка на H2):

    Пример application-prod.yml (условно, PostgreSQL):

    Чтобы включить профиль, можно указать в application.yml:

    Идея простая:

  • dev — удобно разрабатывать, можно автоматически создавать таблицы
  • prod — безопаснее: схема базы должна быть уже подготовлена, приложение её только проверяет
  • Слои приложения: зачем они нужны

    Если смешать в одном месте HTTP-логику, бизнес-правила и SQL — проект быстро станет неудобным.

    В Spring Boot обычно используют слоистую архитектуру:

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

    Роли слоёв

    | Слой | Что делает | Что не должен делать | |---|---|---| | Controller | Принимает HTTP-запрос, вызывает сервис, возвращает HTTP-ответ | Не должен содержать бизнес-логику и работу с базой напрямую | | Service | Содержит бизнес-правила: проверки, расчёты, сценарии | Не должен принимать/формировать HTTP напрямую | | Repository | Делает запросы к базе (через Spring Data JPA) | Не должен решать бизнес-задачи | | Entity | Описывает, как данные хранятся в базе | Не должен быть формой для внешнего API “как есть” | | DTO | Описывает, что именно вы отдаёте/принимаете через API | Не должен быть привязан к тому, как данные лежат в базе |

    Минимальный пример: простой MVP для заметок

    Сделаем самый маленький “скелет”: создать и получить заметки.

    Entity (как храним в базе)

    Важно:

  • Entity — это представление данных в базе
  • @Entity говорит JPA, что это таблица
  • @Id и @GeneratedValue — первичный ключ и его генерация
  • DTO (что отдаём наружу)

    DTO нужен, чтобы:

  • не “светить” внутреннюю модель базы как публичный контракт
  • проще менять базу, не ломая API
  • Repository (доступ к данным)

    Идея Spring Data JPA в том, что базовые операции (save, findById, findAll) уже готовы.

    Service (бизнес-логика)

    Что здесь важно:

  • сервис получает NoteRepository через конструктор — это внедрение зависимостей
  • сервис не знает ничего про HTTP, только про бизнес-операции
  • Controller (REST API)

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

  • @RestController — контроллер, который возвращает данные (обычно JSON)
  • @RequestMapping("/api/notes") — общий путь для всех методов
  • @PostMapping — создание ресурса
  • @GetMapping — чтение ресурсов
  • Это уже MVP: можно создать заметку и получить список.

    Как Spring “склеивает” всё вместе: внедрение зависимостей

    Когда вы пишете @Service, @RestController, @Repository (или наследуетесь от JpaRepository), Spring создаёт бины.

    Бин — это объект, которым управляет Spring: он создаёт его, хранит и передаёт туда зависимости.

    Правило для MVP:

  • используйте внедрение через конструктор
  • Почему это удобно:

  • зависимости явно видны
  • проще тестировать код
  • меньше “магии”
  • Минимальные правила качества для MVP

    Чтобы MVP не превратился в хаос, держитесь простых правил:

  • Контроллеры — только HTTP (параметры запроса, коды ответов, вызов сервиса).
  • Сервисы — только бизнес-логика (проверки, сценарии, транзакции).
  • Репозитории — только доступ к данным.
  • DTO — контракт API, Entity — модель базы.
  • Что будет дальше в курсе

    В следующих статьях мы расширим этот каркас:

  • сделаем нормальные входные DTO для POST (не через @RequestParam)
  • добавим валидацию данных
  • разберём коды ответов, ошибки и единый формат ошибок
  • подключим реальную базу (например, PostgreSQL) и миграции схемы
  • спроектируем REST API более “взросло”: версии, пагинация, фильтрация
  • 2. Разработка REST API: контроллеры, DTO, валидация, обработка ошибок

    Разработка REST API: контроллеры, DTO, валидация, обработка ошибок

    В прошлой статье мы собрали каркас Spring Boot-приложения и разнесли код по слоям: controllerservicerepository, а также разделили Entity и DTO. Теперь сделаем следующий шаг: превратим наш MVP в аккуратный REST API, который:

  • принимает данные через @RequestBody, а не через @RequestParam
  • валидирует входные данные
  • возвращает правильные HTTP-коды
  • отдаёт ошибки в едином формате
  • Полезные официальные источники:

  • Spring Web MVC: Controllers
  • Spring Boot: Servlet Web Applications
  • Bean Validation (Hibernate Validator)
  • RFC 7807: Problem Details for HTTP APIs
  • Как выглядит “взрослый” REST API для MVP

    В MVP хочется быстро сделать “чтобы работало”, но даже минимальный REST API лучше сразу делать понятным:

  • Ресурсы — это сущности, к которым обращаемся по URL (например, заметки: /api/notes).
  • HTTP-методы — это действие:
  • - POST создаёт - GET читает - PUT или PATCH обновляет - DELETE удаляет
  • HTTP-коды показывают результат:
  • - 200 OK — успешное чтение/обновление - 201 Created — ресурс создан - 204 No Content — успешно удалили, тела ответа нет - 400 Bad Request — клиент отправил неверные данные - 404 Not Found — ресурс не найден

    !Диаграмма показывает путь запроса по слоям и как формируются ошибки

    DTO: разделяем вход и выход

    В прошлой статье DTO уже использовались, но теперь разделим их на:

  • Request DTO — что клиент присылает нам (входные данные)
  • Response DTO — что мы возвращаем клиенту
  • Почему это важно:

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

    Контроллер: принимаем JSON через @RequestBody

    В MVP из прошлой статьи мы принимали параметры через @RequestParam. Для реального API обычно удобнее и правильнее принимать JSON-тело запроса.

    Новый контроллер:

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

  • @RequestBody говорит Spring: “возьми JSON из тела запроса и преобразуй в объект”.
  • @Valid запускает валидацию входного DTO.
  • ResponseEntity.created(...) возвращает 201 Created и заголовок Location.
  • Сервис: бизнес-логика и маппинг DTO

    Сервис по-прежнему не знает ничего про HTTP, он просто выполняет операции.

    Добавим несколько методов и нормальную ошибку “не найдено”:

    Исключение:

    Важно: чтобы код выше компилировался, в Note нужны сеттеры (или отдельные методы изменения). Например:

    Валидация: что именно проверяем и где

    Валидация в REST API отвечает на вопрос: “клиент прислал нормальные данные?”

    Обычно проверяют:

  • обязательные поля
  • максимальную длину строк
  • формат (email, uuid, даты)
  • числовые диапазоны
  • Мы уже добавили аннотации в NoteCreateRequest и NoteUpdateRequest:

  • @NotBlank — строка не null и не пустая после обрезки пробелов
  • @Size(max = ...) — ограничение длины
  • Важная связка:

  • @Valid в контроллере включает проверку DTO
  • если DTO не прошло проверку, Spring выбросит MethodArgumentNotValidException
  • Обработка ошибок: единый формат ответов

    Если ошибки обрабатывать “как получится”, клиенту будет неудобно:

  • где-то вернётся 500
  • где-то 400 без подробностей
  • где-то вообще HTML-страница (по умолчанию)
  • Для MVP лучше сделать единый JSON-формат ошибок.

    Простой формат ошибки

    Сделаем DTO для ответа об ошибке:

  • message — общая суть ошибки
  • fieldErrors — ошибки конкретных полей (для валидации)
  • Глобальный обработчик ошибок

    Что мы сделали:

  • @RestControllerAdvice применяет обработчик ко всем контроллерам.
  • NotFoundException превращаем в 404.
  • Ошибки @Valid превращаем в 400 и отдаём ошибки по полям.
  • Неожиданные ошибки превращаем в 500 (и не раскрываем детали клиенту).
  • Как будет выглядеть ошибка валидации

    Например, клиент отправил пустой title. Тогда ответ может быть таким:

    Альтернатива: RFC 7807 (Problem Details)

    Существующий стандарт для ошибок в HTTP API — Problem Details (RFC 7807). Spring умеет работать с этим подходом через ProblemDetail (актуально для Spring Framework 6+).

    Если вы хотите углубиться, начните со стандарта:

  • RFC 7807: Problem Details for HTTP APIs
  • Для MVP допустимо использовать и свой формат (как мы сделали), главное — стабильность и единообразие.

    Минимальные правила качества REST API для MVP

  • Контроллер принимает/возвращает DTO, не Entity.
  • Входные DTO всегда валидируются через @Valid.
  • Ошибки возвращаются в едином JSON-формате.
  • Для “не найдено” всегда используйте 404.
  • Для создания ресурса лучше возвращать 201 Created.
  • Что будет дальше

    Следующие логичные шаги после этой статьи:

  • подключить реальную базу (например, PostgreSQL) и настроить окружения dev/prod
  • добавить миграции схемы (обычно через Flyway)
  • сделать пагинацию и сортировку для списка ресурсов
  • добавить тесты контроллеров и сервисов
  • 3. Базы данных с Spring Data JPA: сущности, связи, миграции, транзакции

    Базы данных с Spring Data JPA: сущности, связи, миграции, транзакции

    В предыдущих статьях мы собрали каркас приложения на Spring Boot и сделали аккуратный REST API: контроллеры, DTO, валидация и единый формат ошибок. Теперь добавим полноценный слой работы с базой данных: научимся правильно описывать сущности (Entity), строить связи между таблицами, управлять схемой через миграции и защищать бизнес-операции транзакциями.

    Полезные официальные источники:

  • Spring Data JPA Reference Documentation
  • Spring Framework Reference: Transaction Management
  • Hibernate ORM Documentation
  • Flyway Documentation
  • Как JPA укладывается в архитектуру из прошлых уроков

    Мы уже разделили ответственность по слоям:

  • контроллеры принимают запросы и возвращают ответы
  • DTO описывают контракт API
  • сервисы содержат бизнес-правила
  • репозитории работают с данными
  • С добавлением базы данных важное правило остаётся тем же:

  • DTO не должны становиться сущностями
  • сущности не должны «уезжать наружу» в JSON
  • Типичный поток данных:

  • Контроллер принимает NoteCreateRequest.
  • Сервис валидирует бизнес-правила и создаёт Entity.
  • Репозиторий сохраняет Entity в базу.
  • Сервис маппит Entity в NoteResponse.
  • !Схема показывает, где используются DTO, а где Entity, и как запрос проходит по слоям.

    Подключение Spring Data JPA и базы данных

    Зависимости

    Обычно нужны:

  • spring-boot-starter-data-jpa
  • драйвер базы данных (например, PostgreSQL)
  • миграции (Flyway)
  • Пример для Gradle:

    Настройки подключения

    Пример application-dev.yml для PostgreSQL:

    Пояснения:

  • ddl-auto: validate означает: Hibernate не создаёт таблицы сам, а проверяет, что схема базы соответствует Entity
  • flyway.enabled: true включает миграции при старте приложения
  • > Для MVP часто хочется ddl-auto: update, но это риск: схема может «поплыть» незаметно. Более надёжный путь: миграции + validate.

    Entity: как описывать таблицы через Java-классы

    Сущность (Entity) — это Java-класс, который JPA сопоставляет с таблицей базы данных.

    Минимальные правила:

  • у сущности должен быть идентификатор @Id
  • сущность должна иметь конструктор без параметров (обычно protected)
  • сущность должна быть согласована со схемой (через миграции)
  • Пример: заметки

    Разбор ключевых аннотаций:

  • @Entity помечает класс как сущность JPA
  • @Table(name = "notes") задаёт имя таблицы (полезно, чтобы не зависеть от правил именования)
  • @Column позволяет указать ограничения столбца
  • GenerationType.IDENTITY удобно для PostgreSQL, когда id генерируется на стороне базы
  • Репозитории Spring Data JPA

    Репозиторий — это интерфейс, который даёт готовые CRUD-операции.

    Что важно:

  • JpaRepository уже содержит save, findById, findAll, deleteById
  • методы вроде existsByTitle Spring Data может сгенерировать по имени
  • Связи между сущностями

    Реальные приложения почти всегда содержат связи:

  • заметка принадлежит пользователю
  • у заметки есть теги
  • у пользователя есть список заметок
  • Виды связей

    | Связь | Пример | Типичные аннотации | |---|---|---| | один-к-одному | профиль пользователя | @OneToOne | | один-ко-многим | пользователь → заметки | @OneToMany и @ManyToOne | | многие-ко-многим | заметки ↔ теги | @ManyToMany |

    Один-ко-многим: пользователь и заметки

    Сделаем две сущности: User и Note. Логика: у заметки есть владелец.

    #### Сущность пользователя

    #### Сущность заметки со ссылкой на владельца

    Ключевые идеи на практике:

  • Владельцем связи считается сторона с @JoinColumn (здесь это Note.owner). Именно она управляет внешним ключом owner_id.
  • FetchType.LAZY означает: пользователь будет загружен по требованию.
  • В User.addNote и User.removeNote мы поддерживаем связь с двух сторон, чтобы не получить рассинхронизацию в памяти.
  • !Диаграмма показывает, где хранится внешний ключ и как выглядит связь один-ко-многим.

    Многие-ко-многим: заметки и теги

    Связь многие-ко-многим обычно реализуется через таблицу связей.

    И добавим теги в Note:

    Практическая рекомендация для MVP:

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

    Миграции — это версионированные SQL-файлы, которые последовательно изменяют схему базы.

    Зачем это нужно:

  • одинаковая схема у всей команды
  • предсказуемые изменения при деплое
  • можно откатить и понять историю изменений
  • Как Flyway ищет миграции

    По умолчанию Flyway ищет файлы в src/main/resources/db/migration.

    Правило именования:

  • V1__init.sql
  • V2__add_tags.sql
  • Пример миграции для пользователей и заметок

    src/main/resources/db/migration/V1__init.sql

    Пример миграции для тегов и таблицы связей

    src/main/resources/db/migration/V2__tags.sql

    Рекомендация по настройкам для MVP и продакшена:

  • Везде включайте Flyway.
  • В продакшене используйте ddl-auto: validate.
  • В разработке тоже можно использовать validate, чтобы рано ловить ошибки схемы.
  • Транзакции: как защитить целостность данных

    Транзакция — это выполнение нескольких действий с базой как одной логической операции.

    Интуитивный смысл:

  • либо применились все изменения
  • либо не применилось ничего
  • В Spring основной инструмент — аннотация @Transactional.

    Где ставить @Transactional

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

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

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

    DTO остаются на уровне API:

    Сервис с транзакцией:

    Что даёт @Transactional здесь:

  • если пользователь не найден или сохранение заметки упадёт с ошибкой, операция целиком считается неуспешной
  • Spring корректно управляет сессией JPA внутри метода
  • Read-only транзакции

    Для операций чтения иногда полезно:

  • чуть оптимизировать работу
  • защититься от случайных изменений
  • Пример:

    Частые ошибки и как их избежать

    Ошибка: LazyInitializationException

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

    Как избежать в MVP:

  • Держите загрузку и маппинг в DTO внутри сервисного метода, где активна транзакция.
  • Для списков используйте запросы с join fetch или EntityGraph, если реально нужны связанные данные.
  • Ошибка: N+1 запрос

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

    Решения на уровне MVP:

  • продумать Response DTO так, чтобы не тянуть лишние связи
  • при необходимости написать запрос с fetch join
  • Пример репозитория с fetch join:

    Ошибка: неправильный Cascade

    cascade = CascadeType.ALL на связях может быть полезен, но его важно понимать:

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

  • каскад часто уместен в @OneToMany от родителя к детям
  • для @ManyToMany каскады используйте осторожно
  • Как это связывается с REST API и обработкой ошибок

    Мы уже сделали NotFoundException и GlobalExceptionHandler. С базой данных это становится ещё важнее:

  • если findById не нашёл запись, сервис бросает NotFoundException, а контроллер возвращает 404
  • если нарушено ограничение уникальности (например, email unique), база вернёт ошибку, и вы можете:
  • - либо обработать её как 400 - либо в MVP оставить как 500, но лучше улучшить позже

    Минимальная рекомендация для MVP:

  • корректно обрабатывайте как минимум 404 и ошибки валидации 400
  • для остальных ошибок держите единый ответ «Внутренняя ошибка сервера», как мы делали в прошлой статье
  • Минимальный чеклист качества для слоя базы данных

  • Entity отражают хранение данных, DTO отражают контракт API.
  • Все изменения схемы делаются через Flyway-миграции.
  • ddl-auto в идеале стоит на validate.
  • Транзакции ставятся на сервисный слой.
  • Связи описаны явно, владелец связи понятен.
  • Что дальше

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

  • пагинация и сортировка списков (Spring Data Pageable)
  • фильтрация (по владельцу, по тегам, по датам)
  • тесты репозиториев и сервисов (например, с Testcontainers)
  • 4. Бизнес-логика и качество кода: сервисы, маппинг, тестирование, документация API

    Бизнес-логика и качество кода: сервисы, маппинг, тестирование, документация API

    В прошлых статьях курса мы уже:

  • собрали структуру Spring Boot-проекта и слои controllerservicerepository
  • сделали аккуратный REST API: DTO, валидация, обработка ошибок
  • подключили базу через Spring Data JPA, добавили связи, Flyway-миграции и транзакции
  • Теперь цель — превратить код из просто работающего в поддерживаемый: чтобы приложение можно было безопасно развивать, не ломая существующее поведение.

    В этой статье разберём четыре практики качества:

  • как проектировать сервисный слой как набор бизнес-сценариев
  • как делать маппинг Entity ↔ DTO без утечек деталей базы в API
  • как выстроить тестирование так, чтобы ловить ошибки рано
  • как сделать документацию API (OpenAPI/Swagger), чтобы вашим API было удобно пользоваться
  • Полезные официальные источники:

  • Spring Framework: Transaction Management
  • Spring Boot Testing
  • JUnit 5 User Guide
  • Mockito Documentation
  • SpringDoc OpenAPI
  • OpenAPI Specification
  • Что такое бизнес-логика и где она живёт

    Бизнес-логика — это правила и сценарии вашего приложения.

    Примеры бизнес-логики (на примере заметок):

  • нельзя создать заметку без владельца
  • заголовок заметки должен быть уникальным для конкретного пользователя
  • удалить заметку может только владелец
  • Это не то же самое, что:

  • валидация входных данных (например, title не пустой)
  • доступ к базе (SQL/JPA)
  • HTTP-детали (коды ответа, заголовки)
  • Правильное место для бизнес-логики в нашей архитектуре — сервисный слой.

    !Схема распределения ответственности по слоям и границе между DTO и Entity

    Сервисный слой как набор сценариев, а не “обёртка над репозиторием”

    Частая ошибка в MVP: сервис превращается в набор методов вида save, findAll, deleteById, которые почти полностью повторяют репозиторий.

    Лучше думать о сервисах как о наборе бизнес-операций (use cases):

  • createNote(ownerId, request)
  • updateNote(noteId, request)
  • deleteNote(noteId, currentUserId)
  • Минимальные правила хорошего сервиса

  • Контроллер не должен знать про JPA и базу данных.
  • Репозиторий не должен решать бизнес-задачи.
  • Сервис возвращает DTO (или доменные результаты), а не отдаёт Entity наружу.
  • Транзакции ставим на методы сервиса.
  • Транзакции в сервисе

    Правило из прошлой статьи сохраняется:

  • @Transactional ставим на методы сервисов, которые меняют данные
  • @Transactional(readOnly = true) можно ставить на операции чтения
  • Пример: удаление заметки с бизнес-проверкой “удалять может только владелец”:

    Ключевая идея: одна транзакция покрывает весь бизнес-сценарий.

    Как отделить “валидацию DTO” от “бизнес-правил”

    Чтобы не путаться:

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

  • @NotBlank title — это валидация
  • “title уникален для пользователя” — это бизнес-правило (обычно проверяется через запрос в базу)
  • Маппинг: как переводить DTO ↔ Entity и не ломать API

    Маппинг — это преобразование объектов между слоями.

    В нашем курсе:

  • контроллер работает с Request DTO и Response DTO
  • сервис внутри использует Entity и репозитории
  • Где делать маппинг

    Есть два популярных подхода для небольшого MVP:

  • делать маппинг прямо в сервисе (быстро и прозрачно)
  • вынести маппинг в отдельный класс mapper/ (чище, удобнее переиспользовать)
  • Для курса выберем второй вариант, чтобы код сервиса был сфокусирован на сценариях.

    Пример: ручной маппинг через отдельный mapper

    NoteMapper:

    Сервис становится проще:

    Обновление: не создавайте новую Entity “поверх старой”

    Практическое правило: при обновлении (PUT/PATCH) сначала загрузите Entity из базы, затем изменяйте поля.

    Так вы:

  • не потеряете существующие поля и связи
  • не создадите случайно новую запись
  • не сломаете JPA-контекст
  • Пример:

    Обратите внимание: здесь можно даже не вызывать save, потому что изменённая сущность будет сохранена при завершении транзакции (механизм dirty checking в JPA). Для MVP допустимо и явно вызывать save, но важно понимать, что это не всегда обязательно.

    Когда стоит подключать MapStruct

    Ручной маппинг — отлично для MVP, но со временем DTO становятся больше.

    Если маппинга много, можно использовать MapStruct:

  • он генерирует код маппинга на этапе компиляции
  • работает быстро (это не reflection)
  • Официальный сайт:

  • MapStruct
  • Для текущего курса достаточно ручного варианта: он проще для понимания и отладки.

    Тестирование: как проверять бизнес-логику и API

    Тесты нужны не “для галочки”, а чтобы:

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

    Идея простая:

  • больше всего быстрых тестов (unit)
  • меньше средних тестов (slice)
  • немного дорогих тестов (integration)
  • Unit-тест сервиса (JUnit + Mockito)

    Unit-тест проверяет бизнес-логику сервиса, подменяя репозитории моками.

    Пример (упрощённо): если заметка не найдена — бросаем NotFoundException.

    Практические замечания для качества:

  • unit-тесты должны быть быстрыми и не поднимать Spring-контекст
  • тестируйте бизнес-ветки: успех, “не найдено”, “запрещено”, конфликты
  • Тест контроллера как web slice (@WebMvcTest)

    @WebMvcTest поднимает только веб-слой (контроллеры) и позволяет проверить:

  • маршруты
  • JSON
  • коды ответов
  • валидацию
  • Пример:

    Здесь мы проверяем именно HTTP-поведение и валидацию, а бизнес-логику сервисов — в unit-тестах.

    Интеграционный тест репозитория (@DataJpaTest)

    @DataJpaTest полезен, чтобы проверить:

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

  • Spring Boot Testing: Test Slices
  • Полная интеграция (@SpringBootTest + Testcontainers)

    Для MVP достаточно нескольких end-to-end тестов, которые поднимают приложение целиком и реальную базу (например, PostgreSQL в контейнере).

    Testcontainers:

  • Testcontainers for Java
  • Важно: такие тесты самые медленные, их должно быть немного, но они дают максимальную уверенность.

    Документация API: OpenAPI и Swagger UI

    Когда API становится больше 2–3 эндпоинтов, документация экономит огромное количество времени:

  • фронтенду
  • мобильной команде
  • вам самим через месяц
  • Подключаем SpringDoc OpenAPI

    Самый удобный вариант для Spring Boot — библиотека SpringDoc.

    Для Gradle (актуальные версии смотрите на сайте SpringDoc):

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

  • Swagger UI: /swagger-ui.html
  • OpenAPI JSON: /v3/api-docs
  • Минимальное документирование контроллера

    Пример:

    Практическая идея: документируйте хотя бы:

  • назначение эндпоинта (summary)
  • успешные ответы (200/201/204)
  • ошибки (400/404)
  • Минимальный чеклист качества для вашего MVP

  • Контроллеры не содержат бизнес-логики и не работают с Entity.
  • Сервисы описывают сценарии и управляют транзакциями.
  • Маппинг DTO ↔ Entity делается в одном месте (в сервисе или mapper-классе), без дублирования.
  • Есть тесты сервисов на ключевые бизнес-ветки.
  • Есть тесты контроллеров на валидацию и коды ответов.
  • OpenAPI/Swagger UI включён, эндпоинты задокументированы.
  • Что дальше по курсу

    Следующий логичный шаг после качества кода:

  • пагинация, сортировка и фильтрация в REST API (Spring Data Pageable)
  • обработка ошибок базы данных (например, уникальные ограничения) как понятные ответы API
  • базовая авторизация (чтобы “владелец заметки” был реальным пользователем)
  • 5. Продакшен-практики: безопасность, логирование, профили, Docker и деплой

    Продакшен-практики: безопасность, логирование, профили, Docker и деплой

    Мы уже научились делать MVP на Spring Boot: строить слои controllerservicerepository, проектировать REST API с DTO и валидацией, подключать базу через Spring Data JPA, добавлять миграции Flyway и транзакции, улучшать качество кода тестами и документацией OpenAPI.

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

    Полезные официальные источники:

  • Spring Boot Reference: Externalized Configuration
  • Spring Boot Reference: Profiles
  • Spring Boot Reference: Logging
  • Spring Security Reference
  • Spring Boot Reference: Container Images
  • Docker Documentation
  • The Twelve-Factor App
  • Что меняется, когда MVP становится продакшеном

    В MVP мы часто делаем упор на скорость разработки. В продакшене добавляются новые требования:

  • Безопасность: закрыть API от посторонних и не утекать секретами.
  • Наблюдаемость: понимать, что происходит в сервисе, по логам и метрикам.
  • Конфигурация: разные настройки для dev и prod без переписывания кода.
  • Воспроизводимый деплой: одинаковая сборка у всех (локально, на сервере, в CI).
  • !Диаграмма показывает путь от кода до продакшен-деплоя и где применяются профили и Docker

    Профили и конфигурация

    Простыми словами: что такое профиль

    Профиль (profile) — это имя набора настроек. Обычно используют:

  • dev — локальная разработка
  • test — тесты
  • prod — продакшен
  • Идея: код один и тот же, а настройки разные.

    Как хранить настройки

    В Spring Boot чаще всего используют файлы:

  • application.yml — общие настройки
  • application-dev.yml — настройки для разработки
  • application-prod.yml — настройки для продакшена
  • Пример application.yml (общие настройки):

    Пример application-dev.yml (локальная база, подробные логи):

    Пример application-prod.yml (логи спокойнее, SQL не печатаем):

    Здесь важный принцип продакшена:

  • секреты не храним в репозитории
  • вместо этого используем переменные окружения (в примере: DB_USERNAME, DB_PASSWORD)
  • Как включить профиль

    Способы включения профиля:

  • через переменную окружения SPRING_PROFILES_ACTIVE=prod
  • через аргумент запуска --spring.profiles.active=prod
  • Пример запуска:

    Когда нужен @Profile

    Иногда нужно включать не только настройки, но и разные бины (объекты Spring) в разных окружениях.

    Пример: в dev хотим тестовые данные, а в prod — нет.

    Логирование: чтобы понимать, что происходит

    Зачем нужны логи

    Логи — это записи о событиях в приложении. Они помогают:

  • быстро понять причину ошибки
  • увидеть, какие запросы приходят
  • отследить проблемные места
  • В Spring Boot обычно используется связка SLF4J + Logback.

    Уровни логов простыми словами

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

    Правило для кода: логируем через log, а не через System.out

    Пример сервиса:

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

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

    Корреляция — это способ связать между собой все логи, относящиеся к одному HTTP-запросу, через идентификатор запроса.

    Минимальный вариант для MVP:

  • принимать заголовок X-Request-Id от клиента или прокси
  • если заголовка нет, генерировать новый
  • добавлять этот id в логи
  • В реальном продакшене это часто делается фильтрами и настройкой логирования. Если вы используете инфраструктуру вроде Kubernetes и централизованный сбор логов, это экономит много времени при разборе инцидентов.

    Безопасность: минимальный набор для MVP в продакшене

    Базовые угрозы, о которых важно помнить

    Если оставить API «как есть», обычно быстро происходят такие проблемы:

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

    Аутентификация и авторизация простыми словами

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

    Минимальная защита через Spring Security

    Самый простой способ закрыть API в MVP — включить Spring Security и потребовать логин/пароль.

    Зависимость:

    Пример минимальной конфигурации (закрываем всё, кроме health-check):

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

  • httpBasic включает простую схему логин/пароль для запросов.
  • csrf.disable() часто применяют для чистых REST API, когда нет cookie-сессий и HTML-форм.
  • permitAll() для /actuator/health полезен, чтобы инфраструктура могла проверять, жив ли сервис.
  • > Если вы не уверены, что именно делаете, не выключайте безопасность «на всякий случай». Лучше начать с базового варианта и осознанно расширять. Spring Security Reference

    CORS: когда фронтенд и бэкенд на разных доменах

    CORS — это правило браузера, которое ограничивает запросы с одного сайта на другой. Это нужно не для серверов, а для браузеров.

    Если у вас фронтенд на https://app.example.com, а API на https://api.example.com, то CORS-настройка может понадобиться.

    Минимальное правило:

  • разрешайте только нужные домены
  • не используйте «разрешить всем» в продакшене
  • Управление секретами

    Что считать секретом:

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

  • храните секреты в переменных окружения
  • не добавляйте секреты в Git
  • не печатайте секреты в логи
  • Actuator: технические эндпоинты для продакшена

    Spring Boot Actuator добавляет эндпоинты для проверки состояния приложения.

    Зависимость:

    Минимальная настройка, чтобы открыть только health:

    Эндпоинт:

  • GET /actuator/health показывает, что приложение живо
  • Важно:

  • не открывайте в интернет всё подряд из actuator
  • открывайте только то, что реально нужно
  • Docker: упаковываем приложение, чтобы оно одинаково запускалось везде

    Зачем Docker

    Docker-образ — это упакованное приложение со всем, что нужно для запуска. Это решает типичные проблемы:

  • «у меня локально работает, а на сервере нет»
  • разные версии Java
  • сложные инструкции для запуска
  • Dockerfile для Spring Boot (через jar)

    Предположим, вы собираете jar:

    Пример Dockerfile:

    Пояснения:

  • eclipse-temurin:21-jre — образ с JRE для запуска
  • JAVA_OPTS позволяет добавлять параметры JVM без пересборки образа
  • Docker Compose для приложения и PostgreSQL

    Пример docker-compose.yml:

    Практические замечания:

  • depends_on не гарантирует, что база уже готова принимать подключения, он только управляет порядком запуска
  • миграции Flyway при старте приложения помогают привести схему в нужное состояние
  • Альтернатива: buildpacks вместо Dockerfile

    Spring Boot умеет собирать контейнерный образ без Dockerfile, через buildpacks.

    Это удобно, когда вы хотите меньше поддерживать инфраструктурный код.

    Смотрите: Spring Boot Reference: Container Images и Cloud Native Buildpacks.

    Деплой: минимальный чеклист

    Конфигурация деплоя по принципам Twelve-Factor

    Хорошее правило для сервисов:

  • конфигурация живёт вне кода
  • приложение не зависит от конкретного сервера
  • Короткий ориентир: The Twelve-Factor App.

    Что нужно подготовить для продакшена

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

  • Профиль prod включается на уровне окружения
  • - через SPRING_PROFILES_ACTIVE=prod.
  • Секреты приходят из окружения
  • - пароли, ключи, токены.
  • Миграции включены
  • - Flyway применяет миграции на старте.
  • Health-check доступен инфраструктуре
  • - /actuator/health.
  • Логи пишутся в stdout
  • - так их проще собирать в Docker/Kubernetes.
  • Ошибки возвращаются в едином формате
  • - как мы делали через GlobalExceptionHandler.
  • Ничего лишнего не открыто наружу
  • - actuator не открыт полностью - Swagger UI в продакшене либо закрыт авторизацией, либо выключен

    !Иллюстрация показывает типичную продакшен-схему и где находятся безопасность, логи и health-check

    Типичные ошибки при переходе к продакшену

    | Ошибка | Почему плохо | Что сделать вместо | |---|---|---| | хранить пароли в application-prod.yml в Git | секреты утекут | переменные окружения или секрет-хранилище | | включить logging.level.root=DEBUG в прод | логи раздуваются и могут утечь данные | INFO, а DEBUG включать точечно | | открывать все actuator-эндпоинты наружу | можно получить доступ к внутренней информации | открывать минимум, обычно только health | | отключить Security «на время» | «время» превращается в навсегда | закрыть API базовой схемой и постепенно улучшать | | использовать ddl-auto: update в прод | схема может измениться непредсказуемо | миграции Flyway + ddl-auto: validate |

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

  • Наши DTO, валидация и GlobalExceptionHandler дают стабильный контракт API и понятные ошибки.
  • JPA, связи и транзакции важны для целостности данных, а Flyway делает изменения базы контролируемыми при деплое.
  • Сервисный слой и тесты уменьшают риск регрессий перед выкладкой.
  • Профили, логирование, безопасность, Docker и health-check — это «упаковка» всего сделанного в форму, готовую для реальной эксплуатации.
  • Что дальше изучать после этой статьи

    Практичные направления развития после продакшен-минимума:

  • расширенная авторизация (роли, права, токены)
  • ограничение частоты запросов (rate limiting) на уровне API gateway или прокси
  • централизованный сбор логов и метрик
  • интеграционные тесты с реальной базой через Testcontainers
  • деплой в Kubernetes (база терминов и стартовая конфигурация)