Spring Boot: MVP-архитектура и разработка REST API с базой данных

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

1. Основы Spring Boot и устройство REST API

Основы Spring Boot и устройство REST API

Зачем этот курс и что вы получите

Этот курс посвящён созданию серверных приложений на Spring Boot, которые предоставляют REST API и работают с базой данных, а также построению кода по принципам MVP-архитектуры (Model-View-Presenter), адаптированной под серверную разработку.

В этой первой статье мы заложим фундамент:

  • разберём, что такое Spring Boot и почему его используют
  • поймём, как устроено REST API
  • увидим базовую структуру проекта и слоёв приложения
  • подготовимся к следующим темам: база данных, миграции, валидация, ошибки, тестирование и MVP
  • > "REST provides a set of architectural constraints that, when applied as a whole, emphasizes scalability of component interactions..." Roy Fielding — Architectural Styles and the Design of Network-based Software Architectures

    Что такое Spring Boot

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

    Ключевые идеи Spring Boot:

  • Автоконфигурация: Spring Boot сам подключает и настраивает многие компоненты, если они есть в зависимостях.
  • Стартеры: готовые наборы зависимостей под конкретную задачу (например, веб, база данных).
  • Встроенный сервер: приложение можно запустить как обычную программу, и веб-сервер уже внутри (обычно Tomcat).
  • Единый способ настройки: конфигурация обычно хранится в application.properties или application.yml.
  • Полезные официальные источники:

  • Spring Boot Reference Documentation
  • Spring Initializr
  • Что такое REST API

    API — это набор правил, по которым одна программа обращается к другой.

    REST API — это популярный стиль построения API поверх протокола HTTP. На практике это означает:

  • у нас есть ресурсы (например, пользователи, товары, заказы)
  • у каждого ресурса есть URL (например, /api/users)
  • действия над ресурсами выражаются HTTP-методами
  • Ресурсы и URL

    Хорошее правило: URL описывает что это за ресурс, а метод HTTP — что сделать.

    Примеры:

  • GET /api/users — получить список пользователей
  • GET /api/users/10 — получить пользователя с id = 10
  • POST /api/users — создать пользователя
  • PUT /api/users/10 — заменить данные пользователя
  • PATCH /api/users/10 — частично изменить пользователя
  • DELETE /api/users/10 — удалить пользователя
  • Методы HTTP и их смысл

    | Метод | Обычно означает | Пример | |---|---|---| | GET | Получить данные | GET /api/users/10 | | POST | Создать новый объект | POST /api/users | | PUT | Полная замена объекта | PUT /api/users/10 | | PATCH | Частичное изменение | PATCH /api/users/10 | | DELETE | Удаление | DELETE /api/users/10 |

    HTTP-статусы

    Сервер отвечает не только данными, но и статус-кодом. Самые важные:

  • 200 OK — успешно
  • 201 Created — успешно создано (часто после POST)
  • 204 No Content — успешно, но тела ответа нет (часто после DELETE)
  • 400 Bad Request — клиент отправил некорректные данные
  • 404 Not Found — ресурс не найден
  • 500 Internal Server Error — ошибка на стороне сервера
  • Как выглядит “поток” запроса в Spring Boot

    Когда клиент (например, фронтенд или Postman) делает запрос, на сервере обычно происходит цепочка:

  • Запрос приходит в Controller
  • Controller вызывает Service (бизнес-логика)
  • Service обращается к Repository (работа с базой данных)
  • Результат возвращается обратно: Repository → Service → Controller → клиент
  • !Диаграмма показывает типовой путь запроса через слои приложения

    Минимальный проект Spring Boot для REST

    Создание проекта

    Самый простой путь — через Spring Initializr. Для начала достаточно:

  • Project: Maven
  • Language: Java
  • Dependencies:
  • - Spring Web - Validation (часто нужно почти сразу)

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

    Точка входа приложения

    Типичный класс запуска:

    @SpringBootApplication включает сразу несколько важных механизмов Spring, в том числе автоматический поиск компонентов.

    Контроллер: входная точка REST API

    Controller отвечает за HTTP-уровень:

  • какие URL доступны
  • какие методы (GET/POST/...) поддерживаются
  • как прочитать параметры запроса и тело запроса
  • какой ответ и статус вернуть
  • Пример простейшего контроллера:

    Здесь:

  • @RestController говорит Spring возвращать данные (часто JSON), а не HTML-страницы
  • @RequestMapping("/api/users") задаёт общий путь
  • @GetMapping обрабатывает GET запрос
  • Сервис: бизнес-логика

    Service — слой, где живут правила приложения. Например:

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

    Пример заготовки сервиса:

    И тогда контроллер вызывает сервис:

    Репозиторий: доступ к данным

    Repository — слой доступа к данным. Позже здесь будет база данных и Spring Data JPA.

    Пока достаточно понять идею:

  • контроллер не должен “лезть” в базу
  • сервис не обязан знать детали хранения
  • репозиторий скрывает конкретный механизм (SQL/JPA/внешнее API)
  • В следующих статьях мы подключим:

  • spring-boot-starter-data-jpa
  • драйвер базы (например, PostgreSQL)
  • миграции (часто используют Flyway)
  • DTO: почему не стоит отдавать наружу ваши внутренние модели

    Когда вы строите API, важно разделять:

  • внутренние классы (то, как данные устроены внутри приложения)
  • контракты API (то, что получает/отправляет клиент)
  • Для контрактов используют DTO (Data Transfer Object): простые классы “для передачи данных”.

    Почему это важно:

  • безопасность: не “утечёт” лишнее поле
  • независимость: внутренние изменения не ломают клиентов
  • удобная валидация входных данных
  • Пример DTO для создания пользователя:

    @NotBlank означает: строка не должна быть пустой или состоять из пробелов.

    @Email означает: строка должна быть похожа на email.

    Пример POST endpoint с DTO

    Здесь:

  • @RequestBody читает JSON из тела запроса
  • @Valid запускает проверку ограничений DTO
  • @ResponseStatus(HttpStatus.CREATED) возвращает 201 Created
  • Где здесь MVP и как его понимать на бэкенде

    Классический MVP чаще применяют в интерфейсах (UI), но его идеи полезны и в серверной разработке как дисциплина разделения ответственности.

    В терминах REST API можно объяснить так:

  • Model (Модель) — данные и бизнес-правила. На практике это доменные классы, сервисы, логика, а позже — сущности JPA.
  • View (Представление) — то, что “видит” клиент: JSON-ответы, структура DTO, поля, форматы.
  • Presenter (Презентер) — “посредник”, который подготавливает данные модели к виду, удобному для клиента. В Spring Boot роль presenter часто выполняют:
  • - отдельные классы-мэпперы (например, преобразование Entity → Response DTO) - слой “facade”/“use case” - иногда сам контроллер (но это хуже масштабируется)

    Практическая цель MVP в нашем курсе:

  • не смешивать HTTP-детали с бизнес-логикой
  • не смешивать бизнес-логику с форматами API
  • делать преобразования данных в одном понятном месте
  • !Поясняет, как MVP можно адаптировать к серверному REST API

    Типовая структура пакетов проекта

    Один из простых и понятных вариантов — группировать код по функциональным областям (например, user, order).

    Пример:

  • com.example.demo.user
  • - UserController - UserService - CreateUserRequest / UserResponse - UserPresenter или UserMapper - позже: UserEntity, UserRepository

    Так легче поддерживать проект: всё, что относится к пользователям, лежит рядом.

    Что важно запомнить из статьи

  • Spring Boot ускоряет разработку за счёт автоконфигурации, стартеров и встроенного сервера.
  • REST API строится вокруг ресурсов, URL и HTTP-методов.
  • В Spring Boot запрос обычно проходит через Controller → Service → Repository.
  • DTO помогают отделить контракт API от внутренних моделей.
  • MVP на бэкенде полезен как принцип: отделять модель (логика) от представления (JSON) через слой подготовки данных (presenter/mapper).
  • Что будет дальше

    В следующих статьях мы начнём строить полноценное API с базой данных:

  • подключим PostgreSQL
  • настроим Spring Data JPA
  • добавим миграции схемы базы данных
  • реализуем CRUD для ресурса
  • оформим ошибки и статусы ответов правильно
  • закрепим MVP: вынесем мэппинг и правила в отдельные компоненты
  • 2. MVP в backend: роли Model, View (DTO) и Presenter

    MVP в backend: роли Model, View (DTO) и Presenter

    Связь с предыдущей статьёй

    В прошлой статье вы увидели базовый путь HTTP-запроса в Spring Boot: Controller → Service → Repository и познакомились с идеей DTO.

    Теперь мы “докрутим” архитектуру: разберём, как применять MVP (Model-View-Presenter) в backend-проекте на Spring Boot так, чтобы:

  • код был проще поддерживать
  • API-контракты (JSON) не смешивались с бизнес-логикой
  • преобразования данных (Entity/Model ↔ DTO) жили в одном понятном месте
  • Почему MVP полезен на бэкенде

    На бэкенде нет “экрана” в UI-смысле, но есть “вид” для клиента: JSON-ответы и форматы запросов. Именно вокруг этого удобно выстроить MVP:

  • Model отвечает за правила и данные приложения
  • View отвечает за то, как API выглядит для клиента (DTO)
  • Presenter соединяет их: превращает данные модели в удобный для клиента вид и наоборот
  • Главная цель: не превращать контроллеры в “комбайны”, которые одновременно валидируют, решают бизнес-задачи и собирают ответы.

    Термины простыми словами

    Model

    Model на backend — это всё, что описывает предметную область и правила.

    Обычно сюда входят:

  • доменные объекты (например, User как понятие “пользователь”)
  • бизнес-логика (например, UserService)
  • доступ к данным (например, UserRepository)
  • Важно:

  • Model не должна знать про HTTP, JSON, статус-коды и структуру ваших DTO
  • View (DTO)

    View на backend — это то, что видит клиент вашего API:

  • DTO запросов (например, CreateUserRequest)
  • DTO ответов (например, UserResponse)
  • формат JSON, названия полей, какие поля доступны клиенту
  • DTO обычно:

  • простые классы “данные без логики”
  • содержат аннотации валидации (@NotBlank, @Email)
  • Presenter

    Presenter — слой-посредник, который:

  • преобразует входные DTO в структуры, понятные Model
  • преобразует результат Model в DTO ответа
  • решает “презентационные” вопросы API-уровня, но без HTTP-деталей
  • Под “презентационными” вопросами в backend обычно понимают:

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

  • Mapper
  • Assembler
  • ResponseFactory
  • Facade или UseCase (если туда добавляют оркестрацию сценариев)
  • Как это связывается со слоями Spring Boot

    Практичная раскладка (которая хорошо масштабируется) выглядит так:

  • Controller принимает HTTP-запрос
  • Controller передаёт DTO в Presenter
  • Presenter преобразует данные и вызывает Model (Service)
  • Service делает бизнес-логику и возвращает результат
  • Presenter превращает результат в Response DTO
  • Controller возвращает DTO клиенту
  • !Схема показывает, где находятся Model, View (DTO) и Presenter в backend-потоке

    Мини-пример: ресурс пользователей

    Ниже пример без базы данных (Repository можно добавить позже). Сейчас важна архитектурная форма.

    View: DTO для запроса и ответа

    DTO запроса:

    DTO ответа:

    Здесь важно:

  • CreateUserRequest описывает что клиент должен прислать
  • UserResponse описывает что клиент увидит в ответе
  • Model: доменный объект и сервис

    Доменный объект (упрощённо):

    Сервис (бизнес-логика):

    Обратите внимание:

  • UserService не принимает CreateUserRequest
  • UserService ничего не знает про JSON и HTTP
  • Presenter: преобразование Model ↔ DTO

    Presenter делает две вещи:

  • получает данные из DTO запроса и вызывает сервис
  • превращает доменный User в UserResponse
  • Почему это удобно:

  • мэппинг сосредоточен в одном месте
  • контроллер становится тонким
  • сервис не зависит от API-форматов
  • Controller: только HTTP-уровень

    Контроллер:

  • принимает запрос
  • запускает валидацию DTO
  • вызывает presenter
  • возвращает статус и DTO
  • Что где должно находиться: краткая таблица

    | Слой | Что делает | Что не делает | Примеры классов | |---|---|---|---| | Controller | HTTP: URL, методы, параметры, статус ответа | Не решает бизнес-задачи, не собирает сложные DTO вручную | UserController | | Presenter | Преобразует DTO ↔ Model, подготавливает данные для ответа | Не работает с HTTP напрямую, не хранит данные | UserPresenter, UserMapper | | Model (Service) | Бизнес-логика и правила | Не зависит от JSON/DTO, не знает про @RequestBody | UserService | | Repository | Доступ к данным | Не содержит бизнес-правил и HTTP-логики | UserRepository | | View (DTO) | Контракт API: вход и выход | Не содержит бизнес-логики | CreateUserRequest, UserResponse |

    Валидация: почему она относится к View (DTO)

    Аннотации типа @NotBlank и @Email проверяют, что клиент прислал корректные данные в рамках контракта API.

    Это естественно держать в DTO, потому что:

  • правила валидации входного JSON относятся к API
  • DTO легко переиспользовать между контроллерами и сценариями
  • При этом бизнес-валидация остаётся в Model, например:

  • “email должен быть уникальным”
  • “пользователя можно удалить только если нет активных заказов”
  • Официальные источники по темам:

  • Документация Spring Boot
  • Справочник Spring Framework по Spring MVC
  • Спецификация Jakarta Bean Validation
  • Типовые ошибки при попытке сделать MVP

    Контроллер становится “толстым”

    Признаки:

  • в контроллере много if, преобразований, ручной сборки JSON
  • контроллер знает детали бизнеса
  • Что делать:

  • вынести оркестрацию и мэппинг в presenter
  • оставить в контроллере только HTTP-склейку
  • Сервис принимает DTO

    Признаки:

  • UserService.create(CreateUserRequest request)
  • Почему плохо:

  • Model становится зависимой от API
  • при изменении DTO придётся менять бизнес-слой
  • Что делать:

  • сервису передавать примитивы или доменные структуры
  • Presenter начинает делать бизнес-логику

    Признаки:

  • в presenter появляются бизнес-правила (“можно/нельзя”, “проверить уникальность”)
  • Что делать:

  • бизнес-правила держать в service
  • в presenter оставить только преобразования и сборку ответа
  • Рекомендуемая структура пакетов

    Один из самых практичных вариантов — группировка по функциональности:

  • com.example.demo.user
  • - api - UserController - CreateUserRequest - UserResponse - presenter - UserPresenter - model - User - UserService - data - UserRepository (появится, когда подключим БД)

    Так все части одного ресурса лежат рядом, а роли читаются по названиям пакетов.

    Итоги

    Вы должны унести из этой статьи:

  • Model на backend: доменные объекты, сервисы, репозитории, бизнес-правила
  • View: API-контракты в виде DTO запросов и ответов
  • Presenter: преобразование DTO ↔ Model и подготовка данных для ответа
  • контроллер становится тонким и предсказуемым: HTTP-вход/выход без бизнес-логики
  • Что дальше по курсу

    Дальше мы подключим базу данных и сделаем этот пример “настоящим”:

  • добавим Spring Data JPA и PostgreSQL
  • введём Entity и Repository
  • покажем, как presenter помогает не “утекать” Entity наружу в API
  • аккуратно оформим ошибки и статусы ответов
  • 3. Доступ к данным: JPA/Hibernate, миграции и транзакции

    Доступ к данным: JPA/Hibernate, миграции и транзакции

    Связь с предыдущими статьями

    В прошлых статьях мы выстроили основу REST API и MVP-подход на бэкенде:

  • Controller отвечает за HTTP
  • View представлена DTO (что получает и возвращает клиент)
  • Presenter превращает DTO в данные для модели и обратно
  • Model (Service) хранит бизнес-правила
  • Теперь добавим настоящие данные: подключим базу, настроим JPA/Hibernate, введём Entity и Repository, научимся управлять схемой через миграции и правильно оформлять транзакции.

    Что такое JPA и Hibernate простыми словами

    JPA (Java Persistence API) — это стандарт (набор интерфейсов и правил), который описывает, как Java-приложение работает с базой данных через объекты.

    Hibernate — самая популярная реализация JPA. Именно Hibernate обычно выполняет реальные SQL-запросы за вас.

    Идея такая:

  • вы описываете таблицу как Java-класс (Entity)
  • Hibernate читает/пишет эти объекты в таблицы
  • Spring Data JPA даёт удобный слой Repository, чтобы не писать одинаковый код доступа к данным
  • Полезные источники:

  • Spring Data JPA Reference Documentation
  • Hibernate ORM Documentation
  • Место JPA в нашей MVP-архитектуре

    Важно заранее разложить ответственности:

  • Entity и Repository относятся к Model (внутренности приложения)
  • DTO относятся к View (контракт API)
  • Presenter обеспечивает преобразования DTO ↔ Model и не отдаёт Entity наружу
  • !Схема показывает, где находятся DTO, Presenter, Service и JPA-слой относительно базы данных

    Ключевое правило курса: не возвращайте Entity напрямую из контроллера. Entity почти всегда “протекает” внутренними полями и связывает ваш API с внутренней схемой данных.

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

    Ниже пример для PostgreSQL. Если у вас другая база, меняется в основном драйвер и URL.

    Зависимости Maven

    Добавьте зависимости:

  • spring-boot-starter-data-jpa
  • org.postgresql:postgresql
  • org.flywaydb:flyway-core
  • Пример pom.xml (фрагмент):

    Настройки application.yml

    Пример минимальной конфигурации:

    Пояснения:

  • ddl-auto: validate просит Hibernate проверять, что схема БД соответствует Entity.
  • validate хорошо сочетается с миграциями, потому что схему создаёт не Hibernate, а Flyway.
  • Логирование org.hibernate.SQL: debug помогает увидеть SQL, который выполняется.
  • Entity: как описать таблицу Java-классом

    Entity — это класс, который Hibernate умеет сохранять в таблицу.

    Пример UserEntity:

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

  • @Entity включает управление Hibernate.
  • @Table(name = "users") задаёт имя таблицы.
  • @Id и @GeneratedValue описывают первичный ключ.
  • protected конструктор без параметров нужен JPA для создания объекта.
  • Repository: доступ к данным без ручного SQL

    Spring Data JPA генерирует реализацию репозитория автоматически.

    Что вы получаете сразу:

  • save(entity)
  • findById(id)
  • findAll()
  • deleteById(id)
  • И можете добавлять методы по имени, например findByEmail.

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

    Если таблицы создать вручную на своём компьютере, а потом унести код на сервер, возникает проблема:

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

    Мы будем использовать Flyway.

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

  • Flyway Documentation
  • Как Flyway понимает, что выполнять

    Flyway хранит в базе служебную таблицу flyway_schema_history.

    Принцип:

  • миграции кладутся в src/main/resources/db/migration
  • файлы именуются как V1__описание.sql, V2__описание.sql
  • Flyway выполняет их по порядку и запоминает, что уже выполнено
  • Пример миграции создания таблицы

    Файл src/main/resources/db/migration/V1__create_users.sql:

    После этого:

  • Flyway создаст таблицу users
  • Hibernate в режиме validate проверит соответствие Entity и таблицы
  • Service: бизнес-логика + работа с Repository

    Service относится к Model. Здесь живут бизнес-правила, а не HTTP.

    Пример:

    Заметьте:

  • сервис принимает простые типы (String, long), а не DTO
  • репозиторий знает про Entity
  • транзакции задаются на уровне сервиса
  • Presenter: не отдаём Entity наружу

    DTO относятся к View. Presenter превращает Entity в Response DTO.

    DTO ответа:

    Presenter:

    Здесь Entity остаётся внутри приложения.

    Controller: тонкий HTTP-слой

    Контроллер принимает DTO, валидирует и отдаёт DTO.

    Транзакции: что это и зачем

    Транзакция — это режим выполнения операций с базой данных, когда они воспринимаются как единое целое:

  • либо все изменения фиксируются
  • либо, если произошла ошибка, изменения откатываются
  • Почему это важно в реальном API

    Типичные ситуации, где транзакция обязательна:

  • создание сущности и связанных записей
  • проверка условий и последующая запись
  • изменение нескольких таблиц в одном бизнес-сценарии
  • !Схема показывает границы транзакции и варианты commit/rollback

    Как Spring управляет транзакциями

    Чаще всего используется аннотация @Transactional из Spring.

    Важно понимать:

  • обычно транзакцию ставят на методы сервиса, а не контроллера
  • @Transactional(readOnly = true) полезен для операций чтения
  • по умолчанию Spring откатывает транзакцию при RuntimeException и Error
  • Официальный источник:

  • Spring Framework Reference: Transaction Management
  • Практические правила для курса

    Чтобы проект был предсказуемым:

  • ставьте @Transactional на методы Service, которые меняют данные
  • ставьте @Transactional(readOnly = true) на методы Service, которые только читают
  • не делайте “бизнес-логические записи” в Presenter или Controller
  • Частые ошибки при подключении JPA

    Выставлять ddl-auto: create в продакшене

    Проблема: Hibernate может пересоздать схему и стереть данные.

    Решение: использовать Flyway и ddl-auto: validate (или none, если вы уверены в миграциях).

    Отдавать Entity в API

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

    Решение: DTO + Presenter.

    Держать транзакции “где попало”

    Проблема: часть данных успела сохраниться, часть нет.

    Решение: транзакции в сервисе, где собрана бизнес-операция.

    Рекомендуемая структура пакетов (с учётом БД)

    Один ресурс рядом, роли читаются по пакетам:

  • com.example.demo.user
  • - api - UserController - CreateUserRequest - UserResponse - presenter - UserPresenter - model - UserService - data - UserEntity - UserRepository

    Итоги

    В этой статье вы собрали полный “скелет” доступа к данным:

  • JPA — стандарт, Hibernate — реализация, Spring Data JPA — удобные репозитории
  • Entity и Repository относятся к Model и не должны “утекать” в API
  • Flyway хранит историю схемы БД и воспроизводимо применяет миграции
  • @Transactional задаёт границы атомарности бизнес-операций и должен жить в Service
  • Что дальше

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

  • корректно оформлять ошибки API (400/404/409) и сообщения
  • реализовать полный CRUD и поиск
  • добавить пагинацию и сортировку
  • начать писать тесты слоя Repository и Service
  • 4. Качество API: валидация, ошибки, безопасность и документация

    Качество API: валидация, ошибки, безопасность и документация

    Связь с предыдущими статьями

    Ранее в курсе мы построили основу приложения на Spring Boot:

  • разобрали, как устроено REST API и слои Controller → Service → Repository
  • договорились об MVP-подходе на бэкенде: Model (логика), View (DTO), Presenter (преобразования)
  • подключили базу данных через JPA/Hibernate, добавили миграции Flyway и научились ставить транзакции на сервисы
  • Теперь сделаем следующий критический шаг: улучшим качество API.

    Под качеством здесь будем понимать 4 вещи:

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

    Валидация

    Зачем нужна валидация

    Валидация отвечает на вопрос: корректно ли клиент сформировал запрос по правилам контракта API.

    Примеры типичных проблем:

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

  • API-валидацию (формат, обязательность, длина) — обычно в DTO, то есть во View
  • бизнес-валидацию (уникальность email, запрет удаления при активных заказах) — в Service, то есть в Model
  • Официальные источники:

  • Jakarta Bean Validation
  • Spring Framework Reference: Validation
  • DTO и Bean Validation

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

    Что означают аннотации:

  • @NotBlank запрещает null, пустую строку и строку из пробелов
  • @Size(max = ...) ограничивает длину строки на уровне контракта
  • @Email проверяет, что строка похожа на email
  • В контроллере валидация запускается через @Valid:

    Если DTO не проходит валидацию, Spring вернёт ошибку автоматически. Но качество API сильно зависит от того, в каком формате вы эту ошибку отдаёте и какой статус выбираете.

    Бизнес-валидация в сервисе

    Пример правила: email должен быть уникальным. Это не про формат запроса, это про данные и правила предметной области.

    EmailAlreadyUsedException — пример доменного исключения, которое позже мы красиво превратим в HTTP-ответ.

    Ошибки и правильные HTTP-статусы

    Почему важно стандартизировать ошибки

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

  • показывать понятные сообщения пользователю
  • отличать “не найдено” от “не прошло правило”
  • автоматически обрабатывать типовые ситуации
  • Цель: сделать ошибки предсказуемыми.

    Какие статусы использовать чаще всего

    | Ситуация | Статус | Что это значит | |---|---:|---| | DTO не прошло валидацию | 400 Bad Request | Клиент отправил некорректный запрос | | Ресурс не найден | 404 Not Found | По указанному идентификатору данных нет | | Конфликт с текущим состоянием (например, email занят) | 409 Conflict | Запрос корректный, но нарушает ограничения домена | | Нет прав | 403 Forbidden | Пользователь аутентифицирован, но доступ запрещён | | Не аутентифицирован | 401 Unauthorized | Нужна аутентификация | | Неожиданная ошибка на сервере | 500 Internal Server Error | Ошибка в коде или инфраструктуре |

    Единый формат ошибок

    Хорошая практика — вернуть единый JSON-формат ошибки.

    В Spring 6 и Spring Boot 3 есть удобный тип ProblemDetail, который соответствует идее “problem details”. Официальный стандарт: RFC 7807: Problem Details for HTTP APIs.

    Пример целевого ответа при ошибке:

    Глобальный обработчик ошибок

    Вместо того чтобы ловить исключения в каждом контроллере, делается один класс @RestControllerAdvice.

    Пример:

    Что важно в этом подходе:

  • контроллеры остаются тонкими
  • сервисы выбрасывают доменные исключения
  • HTTP-статусы выбираются централизованно
  • Доменные исключения

    Примеры исключений (коротко):

    Безопасность API

    Минимальная модель безопасности

    Безопасность почти всегда состоит из двух частей:

  • аутентификация: кто ты
  • авторизация: что тебе разрешено
  • Даже если вы делаете учебное API, полезно сразу прививать привычки:

  • не отдавать чувствительные данные
  • не доверять входным данным
  • явно закрывать доступ к “внутренним” эндпоинтам
  • Официальные источники:

  • Spring Security Reference
  • Spring Boot Reference Documentation
  • Подключение Spring Security

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

  • Maven: spring-boot-starter-security
  • После подключения, по умолчанию, многие эндпоинты станут требовать аутентификацию. Это нормально, но вам нужно осознанно настроить правила.

    Базовая конфигурация SecurityFilterChain

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

  • Swagger и OpenAPI доступны без логина
  • /api/** требует аутентификацию
  • используем HTTP Basic как учебный вариант
  • Пояснения простыми словами:

  • SecurityFilterChain задаёт правила доступа
  • httpBasic включает простой механизм логина для тестирования
  • csrf.disable() часто применяют для stateless REST API, но включение или отключение CSRF зависит от того, как вы аутентифицируетесь и используете ли браузерные cookies
  • Пароли: только хэши

    Если в проекте появляются пароли пользователей:

  • никогда не храните пароль в базе в открытом виде
  • храните хэш, обычно через BCrypt
  • В Spring Security стандартный вариант:

    Авторизация по ролям

    Если вам нужно ограничить доступ, например, “только админ может удалять”, то обычно делают роли.

    Идея:

  • у пользователя есть роль ADMIN или USER
  • в правилах указывается: кто может вызвать endpoint
  • Это можно делать через настройки authorizeHttpRequests или через аннотации @PreAuthorize.

    Официальный источник для метода с аннотациями: Spring Security Reference: Method Security

    CORS

    Если фронтенд (браузер) будет жить на другом домене, вам может понадобиться CORS-настройка.

    Ключевой принцип:

  • разрешайте только нужные origin
  • не ставьте “разрешить всё” в продакшене
  • Официальный источник: Spring Framework Reference: CORS

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

    Зачем нужна документация

    Документация решает практические проблемы:

  • фронтенд понимает, какие эндпоинты есть и какие поля отправлять
  • QA быстро тестирует API
  • вы сами меньше “помните в голове”, больше опираетесь на контракт
  • OpenAPI и Swagger UI

    На Spring Boot популярный вариант — springdoc-openapi.

    Официальный сайт проекта: springdoc-openapi

    Пример зависимости Maven:

    После подключения обычно доступны:

  • OpenAPI JSON: /v3/api-docs
  • Swagger UI: /swagger-ui/index.html
  • Минимальная аннотация для endpoint

    Можно добавлять описания прямо в контроллер:

    Как всё это укладывается в MVP

    Сопоставим требования качества с нашей архитектурой:

    | Задача качества | Где должна жить | Почему | |---|---|---| | Валидация формата запроса | DTO (View) | Это часть контракта API | | Бизнес-правила (уникальность, ограничения) | Service (Model) | Это правила предметной области | | Маппинг Entity ↔ DTO | Presenter | Чтобы Entity не “утекали” наружу | | Формат ошибок и статусы | GlobalExceptionHandler (HTTP-уровень рядом с Controller) | Это ответственность API-слоя | | Авторизация доступа к endpoint | Конфигурация Spring Security и/или аннотации | Это поперечная функциональность | | Документация | Контроллеры и DTO через OpenAPI | Описываем контракт API |

    Практический чек-лист качества

    Перед тем как считать endpoint готовым, полезно проверить:

  • DTO запроса имеет @Valid и нужные ограничения (@NotBlank, @Size, @Email)
  • бизнес-валидация находится в сервисе и покрыта доменными исключениями
  • ошибки возвращаются в едином формате и с правильными статусами (400, 404, 409)
  • Entity не возвращается наружу, только Response DTO
  • есть базовые правила безопасности (как минимум закрыт /api/**)
  • endpoint виден в Swagger UI и имеет понятное описание
  • Итоги

    В этой статье вы сделали API заметно более профессиональным:

  • разделили API-валидацию и бизнес-валидацию по слоям
  • ввели единый подход к ошибкам через глобальный обработчик и понятные HTTP-статусы
  • подключили базовые принципы безопасности: аутентификация, правила доступа, хэширование паролей
  • добавили документацию через OpenAPI и Swagger UI
  • Что дальше

    Логичное продолжение после повышения качества API:

  • написать тесты для контроллеров, сервисов и репозиториев
  • добавить пагинацию, сортировку и фильтрацию
  • аккуратно оформить версионирование API и совместимость контрактов
  • 5. Тестирование и подготовка к запуску: профили, конфигурация, деплой

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

    Связь с предыдущими статьями

    Ранее мы построили основу REST API на Spring Boot и договорились о дисциплине MVP на бэкенде:

  • Controller отвечает за HTTP-уровень
  • View — это DTO запросов и ответов
  • Presenter делает преобразования DTO ↔ Model и не даёт Entity “утекать” наружу
  • Model (Service + Repository + Entity) содержит бизнес-правила, доступ к данным, транзакции
  • Качество API поддерживается валидацией, стандартизированными ошибками, безопасностью и документацией
  • Теперь нам нужно довести приложение до состояния готово к запуску: уметь уверенно проверять поведение через тесты, управлять конфигурацией для разных окружений и понимать базовый путь деплоя.

    !Общая карта: как тесты, профили и деплой “накрывают” архитектуру проекта

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

    Без тестов и нормальной конфигурации проект быстро становится “хрупким”:

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

  • Быстро проверять изменения тестами.
  • Иметь разные конфигурации для dev, test, prod.
  • Собирать и запускать приложение одинаково локально и на сервере.
  • Тестирование Spring Boot API

    Тесты полезно разделять по “границам ответственности”, которые мы уже ввели через MVP и слои.

    Виды тестов в backend-проекте

    Коротко о том, что вы будете использовать чаще всего:

  • Unit-тесты — проверяют один класс изолированно (например, Service или Presenter).
  • Slice-тесты — поднимают небольшой кусок Spring-контекста (например, только Web слой или только JPA слой).
  • Интеграционные тесты — поднимают почти всё приложение и проверяют работу вместе (часто с настоящей БД в контейнере).
  • Полезные источники:

  • Документация Spring Boot: Testing
  • JUnit 5 User Guide
  • Mockito Documentation
  • Testcontainers Documentation
  • !Визуальная “пирамида”: каких тестов должно быть больше, а каких меньше

    Unit-тесты: Service и Presenter

    В нашей архитектуре это самые “выгодные” тесты:

  • Service содержит бизнес-правила
  • Presenter содержит мэппинг и сборку ответов
  • #### Что тестировать в Service Примеры бизнес-проверок:

  • “email должен быть уникальным”
  • “если пользователя нет — выбросить UserNotFoundException”
  • “нельзя изменить поле X при условии Y”
  • Практический подход:

  • Мокаем UserRepository через Mockito.
  • Вызываем метод сервиса.
  • Проверяем результат или выброшенное исключение.
  • Пример (упрощённо):

    #### Что тестировать в Presenter Presenter удобно тестировать без Spring-контекста:

  • корректно ли он вызывает Service
  • корректно ли он преобразует Entity/Model в Response DTO
  • Пример идеи проверки:

    Важно: в реальном проекте у UserEntity обычно есть id, который появляется после save, поэтому в unit-тестах часто проще маппить не Entity, а доменную модель, или подставлять объект с ожидаемыми полями.

    Slice-тесты

    Slice-тесты включают только часть Spring Boot и поэтому:

  • работают быстрее интеграционных
  • дают больше уверенности, чем unit-тесты на “чистых” моках
  • #### Тесты Web-слоя: @WebMvcTest @WebMvcTest поднимает контроллеры и Spring MVC, но не поднимает сервисы/репозитории автоматически.

    Когда полезно:

  • проверить HTTP-статусы (201, 400, 404)
  • проверить валидацию DTO (@Valid)
  • проверить формат JSON ответа
  • проверить работу @RestControllerAdvice (ошибки)
  • Официальный источник: Документация Spring Boot: Testing Spring MVC

    #### Тесты JPA-слоя: @DataJpaTest @DataJpaTest поднимает JPA и репозитории.

    Когда полезно:

  • проверить запросы репозитория (findByEmail, existsByEmail)
  • убедиться, что ограничения БД работают ожидаемо
  • Официальный источник: Документация Spring Boot: Testing Data Access

    Интеграционные тесты: @SpringBootTest + настоящая БД

    Интеграционный тест проверяет сценарий “почти как в продакшене”:

  • поднимается Spring Boot контекст
  • выполняются миграции Flyway
  • запрос проходит Controller → Presenter → Service → Repository → БД
  • Лучший учебно-практический способ получить настоящую БД в тестах — Testcontainers.

    #### Зачем Testcontainers Если вы используете H2 “вместо PostgreSQL”, вы можете получить ложную уверенность:

  • разные типы данных
  • разные ограничения
  • различия в SQL-диалекте
  • Testcontainers поднимает PostgreSQL в Docker-контейнере прямо на время тестов.

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

  • Testcontainers: PostgreSQL Module
  • Документация Spring Boot: Testcontainers
  • Пример структуры интеграционного теста (концептуально):

    Дальше вы расширяете сценарии: делаете HTTP-запросы (например, через TestRestTemplate или WebTestClient) и проверяете результат.

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

    Одна из главных причин проблем при запуске — “разные настройки в разных окружениях”. Spring Boot решает это через профили и внешнюю конфигурацию.

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

  • Документация Spring Boot: Externalized Configuration
  • Документация Spring Boot: Profiles
  • Что такое профиль

    Профиль — это имя набора настроек, например:

  • dev — локальная разработка
  • test — тестовое окружение
  • prod — продакшен
  • В Spring Boot это обычно означает:

  • разные значения в application-<profile>.yml
  • включение/выключение некоторых бинов через @Profile
  • Файлы конфигурации: application.yml и application-<profile>.yml

    Рекомендуемая практика:

  • application.yml — общие настройки
  • application-dev.yml — локальная разработка (удобные логи, локальная БД)
  • application-test.yml — тесты (если нужно)
  • application-prod.yml — продакшен (безопасность, минимальные логи, реальные подключения)
  • Пример (идея структуры):

    Как включить профиль

    Самые частые способы:

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

    Порядок приоритета настроек

    Важный принцип: Spring Boot позволяет переопределять настройки “снаружи”, не меняя код.

    Практическое правило, которое стоит запомнить:

  • переменные окружения и аргументы командной строки обычно имеют более высокий приоритет, чем файлы application.yml
  • Это удобно для деплоя: секреты (пароли) не должны лежать в репозитории.

    !Как настройки могут переопределять друг друга между файлами и окружением

    Типизированная конфигурация через @ConfigurationProperties

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

    Пример:

    И конфигурация:

    Плюсы:

  • меньше ошибок из-за опечаток
  • настройки читаются как часть контракта приложения
  • Официальный источник: Документация Spring Boot: Configuration Properties

    Секреты и чувствительные данные

    Что нельзя хранить в репозитории:

  • пароли к БД
  • токены
  • приватные ключи
  • Как правильно для простого деплоя:

  • хранить их в переменных окружения
  • прокидывать через секреты вашего окружения (например, в CI/CD или в платформе деплоя)
  • Подготовка к запуску

    Под “готовностью к запуску” будем понимать: приложение собирается в один артефакт, стартует, применяет миграции, подключается к БД и умеет сообщать, что оно живо.

    Сборка приложения

    Типичный результат сборки Spring Boot — исполняемый jar.

    Пример команд для Maven:

    Запуск:

    Проверки перед запуском

    Минимальный чек-лист:

  • Flyway миграции лежат в src/main/resources/db/migration
  • spring.jpa.hibernate.ddl-auto не стоит в create или create-drop для реальных окружений
  • в продакшене конфигурация БД приходит из переменных окружения
  • Entity не отдаются наружу (только DTO)
  • ошибки стандартизированы через @RestControllerAdvice
  • Actuator: здоровье приложения и диагностика

    Spring Boot Actuator добавляет технические endpoints (здоровье, метрики и т.д.).

    Официальный источник: Документация Spring Boot: Actuator

    Подключение

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

    Минимальная настройка

    Например, открыть health:

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

  • деплой-система может проверять /actuator/health
  • проще понимать, живо ли приложение и есть ли проблемы с БД
  • Важно: в реальном продакшене внимательно настраивайте доступ к actuator endpoints.

    Деплой: базовые варианты

    Ниже самые типовые способы “донести” Spring Boot приложение до сервера.

    Вариант: запуск jar на сервере

    Подходит для простого VPS:

  • Собрать jar.
  • Скопировать на сервер.
  • Запустить с нужными переменными окружения.
  • Пример:

    Вариант: Docker

    Docker помогает стандартизировать окружение.

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

  • Docker Documentation: Get Started
  • Документация Spring Boot: Container Images
  • Простейшая идея Dockerfile (концептуально):

    И запуск с переменными окружения:

    Вариант: Docker Compose (приложение + PostgreSQL)

    Это удобный способ поднять полный стенд.

    Официальный источник: Docker Compose Documentation

    Идея состава:

  • сервис db (PostgreSQL)
  • сервис app (ваш Spring Boot)
  • В этом случае важно:

  • Flyway миграции применяются при старте приложения
  • приложение ждёт доступность БД (обычно решается healthcheck и политикой рестарта)
  • Итоги

    Вы собрали практический “пояс безопасности” вокруг Spring Boot API:

  • понимаете, какие тесты писать для Service/Presenter/Controller/Repository
  • умеете разделять конфигурации через профили и application-<profile>.yml
  • выносите секреты в переменные окружения
  • знаете базовый путь сборки и запуска jar
  • понимаете, как подойти к Docker-деплою
  • можете добавить Actuator для проверки здоровья приложения
  • В следующем шаге курса логично закрепить это практикой: написать набор тестов под ваш ресурс (например, users), а затем поднять стенд через Docker Compose и убедиться, что миграции и профили работают корректно.