Архитектура современных Java-приложений: полный цикл разработки на Spring

freeCodeCamp.org 919 тыс. 3 ч 30 мин 25 мин 18.03.2024
Главное

«Используйте те инструменты, которые позволяют вам достичь продуктивности, будь то хоть блокнот или терминал с Vim», — призывает Дэн Вега, напоминая, что за технологическим шумом современного Spring Boot 3 важно не потерять суть разработки. Этот интенсивный гайд превращает хаос конфигураций в стройную архитектуру, проходя путь от простой инициализации проекта до создания масштабируемых REST API, интегрированных с Docker и PostgreSQL.

🚀 Введение в разработку на Spring Boot 3 0:27

Этот курс представляет собой практическое руководство по созданию современных веб-приложений на Java с использованием фреймворка Spring Boot 3. Основная задача обучения — пройти путь от настройки окружения до реализации полноценного REST API, взаимодействующего с базой данных, и обеспечения качества кода через комплексное тестирование. Автор курса, Дэн Вега (Dan Vega), Spring Developer Advocate в Broadcom и Java Champion, подчеркивает, что цель программы — дать необходимые навыки для самостоятельной разработки приложений, которые слушатели смогут применить в своих собственных проектах, будь то трекеры тренировок, кулинарные рецепты или любые другие идеи.

Необходимый стек технологий и инструменты 0:27

Для эффективного прохождения курса и дальнейшей работы с экосистемой Spring разработчику потребуется определенный набор инструментов. Основой является язык Java: для Spring Boot 3 необходимо наличие JDK 17 или выше (рекомендуется использование актуальных версий, таких как 21). Для управления версиями Java на macOS крайне полезен инструмент SDKMAN!, позволяющий параллельно устанавливать и переключаться между различными SDK.

Ключевые элементы рабочего окружения:

Экосистема Spring и начало работы с Initializer 11:39

Spring — это обширный и гибкий фреймворк, охватывающий практически все аспекты разработки современного ПО: от микросервисов и реактивных веб-приложений до серверных функций, пакетной обработки данных и даже CLI-приложений. Популярность фреймворка обусловлена его надежностью, постоянной поддержкой со стороны крупной инженерной команды и бесшовной интеграцией различных модулей (например, Spring Cloud для распределенных систем). При возникновении вопросов первым источником истины всегда должна быть официальная документация на spring.io, которая обновляется чаще, чем сторонние статьи.

Процесс создания проекта начинается с использования Spring Initializer (start.spring.io). Этот сервис позволяет:

  1. Выбрать систему сборки (Maven/Gradle) и язык (Java, Kotlin, Groovy).
  2. Указать версию Spring Boot (актуальная на момент записи — 3.2.3).
  3. Задать параметры проекта (Group, Artifact).
  4. Добавить начальные зависимости, такие как Spring Web (для построения API) и Spring Boot DevTools (для ускорения разработки).

После генерации проекта и его открытия в IDE, разработчик получает стандартную структуру Maven-проекта с разделением на исходный код (src/main/java) и ресурсы (src/main/resources), готовую к написанию первого функционала.

🛠️ Анатомия Spring Boot: от структуры проекта до инверсии управления и Java Records 25:13

Структура проекта, Maven Wrapper и магия Dev Tools 25:13

Разбор устройства типичного приложения на Spring Boot начинается с его файловой структуры. Внутри проекта четко разделены директории для основного кода и ресурсов (src/main) и тестовых классов (src/test), которые зеркально повторяют структуру пакетов приложения. Важным элементом современной экосистемы является утилита Maven Wrapper (mvnw). Её наличие гарантирует, что разработчику не нужно вручную устанавливать Maven на локальную машину: встроенная обертка автоматически скачает всё необходимое для сборки. Это значительно упрощает совместную работу, когда проект передается между членами команды.

Сердцем сборки выступает файл pom.xml, где декларируются все зависимости. Дэн Вега обращает внимание на концепцию Spring Boot Starters — специальных стартовых наборов, которые агрегируют в себе множество подзависимостей, избавляя программиста от рутины по их поиску и ручной настройке. Запуск приложения возможен как из среды разработки (IDE) кнопкой «Play», так и через терминал с помощью команды ./mvnw spring-boot:run. Процесс разработки не всегда проходит гладко: Дэн демонстрирует типичную ошибку конфликта сетевых портов и оперативно решает её через встроенную утилиту JPS, завершая зависший процесс по его идентификатору.

Из коробки в Spring Boot уже настроено логирование на базе библиотеки SLF4J. Разработчику достаточно объявить логгер через фабрику, чтобы выводить кастомные сообщения с различными уровнями (INFO, DEBUG, WARN). Дополнительное удобство при локальной разработке предоставляет модуль Spring Boot Dev Tools. Он обеспечивает две важнейшие функции:

Инверсия управления: почему стоит забыть про ключевое слово new 31:58

Переходя к написанию бизнес-логики, Дэн затрагивает фундаментальную концепцию Spring — инверсию управления (Inversion of Control, IoC). Чтобы наглядно продемонстрировать её суть, он создает простой класс WelcomeMessage в произвольном пакете foo.bar. Если инстанцировать этот класс вручную, используя классическое Java-выражение var welcomeMessage = new WelcomeMessage(), программа успешно отработает, но управление жизненным циклом объекта останется на стороне разработчика. В масштабных корпоративных приложениях такой подход быстро превращается в неуправляемый хаос.

«Каждый раз, когда вы видите ключевое слово new, скорее всего, в контексте Spring вы делаете что-то не так», — напоминает Дэн. Идея IoC заключается в том, чтобы полностью передать обязанность по созданию, конфигурации и связыванию объектов самому фреймворку. Все управляемые объекты живут внутри огромного контейнера, который называется Application Context.

В терминологии Spring такой управляемый объект называется бином (Bean). Бин — это обычный экземпляр класса, вокруг которого фреймворк выстраивает дополнительную метаинформацию для управления его жизненным циклом. Однако попытка Дэна запросить бин WelcomeMessage напрямую из контекста поначалу оборачивается ошибкой. Причина кроется в правилах сканирования компонентов: класс был создан за пределами основного пакета приложения. Как только Дэн переносит WelcomeMessage внутрь базового пакета (dev.danvega), механизм Component Scanning автоматически обнаруживает класс, превращает его в Bean, и инверсия управления начинает работать безупречно.

Проектирование фичи и современная модель данных на Java Records 41:24

В качестве практического примера Дэн предлагает разработать приложение для трекинга физической активности и бега. Процесс создания новой фичи обнажает важный вопрос архитектуры: как именно структурировать пакеты в проекте? Существует два основных подхода:

Дэн отдает предпочтение подходу Package by Feature, поскольку он позволяет использовать область видимости package-private, благодаря чему компоненты внутри пакета могут свободно общаться друг с другом, оставаясь скрытыми от остального приложения.

Моделируя сущность Run (забег), лектор сначала демонстрирует старый «многословный» способ Java: объявление приватных полей (ID, название, время старта и окончания, мили), создание гигантского конструктора, геттеров, сеттеров, методов equals(), hashCode() и toString(). Это генерирует около 100 строк шаблонного кода (boilerplate).

Современная Java позволяет кардинально решить эту проблему с помощью записей (Java Records). Заменив класс на record Run, Дэн сокращает объем кода до 12 легко читаемых строк. Записи по своей природе неизменяемы (immutable): у них нет сеттеров, а значения полей можно задать только при создании экземпляра. При этом компилятор автоматически генерирует конструктор со всеми аргументами, методы сравнения и строковое представление. В дополнение к модели создается перечисление Location с фиксированными значениями INDOOR и OUTDOOR для указания типа тренировки. Чтобы мгновенно протестировать модель, Дэн использует интерфейс CommandLineRunner, код которого автоматически выполняется сразу после инициализации контекста приложения, успешно выводя созданный объект забега в консоль.

В самом конце этого этапа Дэн отмечает, что встроенный сервер Tomcat успешно запустился на порту 8080, что подводит нас к теме веб-приложений и Spring MVC, которая будет подробно разобрана в следующей главе.

🌐 Создание REST API и архитектура Spring MVC 50:07

Приложение на Spring Boot требует настройки точек входа (endpoints), через которые оно будет взаимодействовать с внешним миром. Для этой цели в экосистеме Spring используется паттерн MVC (Model-View-Controller).

Модель (Model) в нашем случае — это доменная сущность, данные, с которыми мы работаем. Ранее в разговоре они касались структуры проекта и разработки модели данных. Представление (View) при создании REST API — это не привычная HTML-страница, а данные в формате JSON, которые возвращаются пользователю.

Центральным элементом системы является контроллер (Controller) — своего рода «регулировщик» трафика. Его задача максимально проста: принять HTTP-запрос, при необходимости делегировать обработку бизнес-логики другим классам (сервисам или репозиториям) и вернуть ответ. Контроллеры не должны заниматься управлением данными напрямую — они лишь связующее звено.

🛠 Аннотации и маппинг запросов 51:51

В Java-разработке на Spring широко используются аннотации. Они позволяют добавлять поведение классам или методам, значительно сокращая объем шаблонного кода.

Для создания контроллера мы используем аннотацию @RestController. Она сообщает Spring, что данный класс является REST-контроллером и по умолчанию должен возвращать тело ответа в формате JSON. Чтобы связать метод контроллера с конкретным URL, применяются аннотации маппинга:

Например, добавление @GetMapping("/hello") над методом позволяет приложению отвечать на GET-запрос по адресу /hello, возвращая строковое значение.

💾 Реализация CRUD-операций и инверсия управления 54:03

Для реализации полноценного CRUD (Create, Read, Update, Delete) API мы переносим логику хранения данных в отдельный класс — RunRepository. Это позволяет отделить управление данными от обработки запросов. Временно мы используем хранилище в оперативной памяти (в виде ArrayList), которое инициализируется при запуске приложения с помощью метода, помеченного аннотацией @PostConstruct.

Основополагающим принципом здесь является инверсия управления (IoC). Мы не создаем экземпляры репозитория через ключевое слово new внутри контроллера. Вместо этого мы используем Dependency Injection (внедрение зависимостей): контроллер запрашивает экземпляр RunRepository через конструктор, а Spring автоматически предоставляет уже готовый объект, которым он управляет.

🚀 Динамические пути и работа с телом запроса 1:06:11

Чтобы сделать API гибким, мы используем динамические переменные в путях. Аннотация @PathVariable позволяет извлечь идентификатор из URL и передать его в метод контроллера.

При работе с поиском конкретной записи, вместо того чтобы просто возвращать объект, который может быть null, мы используем Optional<Run>. Это позволяет элегантно обрабатывать отсутствие данных: например, если запись не найдена, мы выбрасываем ResponseStatusException с кодом 404 (Not Found), что является стандартом для корректного API.

Для создания новых записей через HTTP POST-запросы мы используем аннотацию @RequestBody. Она указывает Spring, что данные, приходящие в теле запроса (в формате JSON), должны быть автоматически десериализованы в соответствующий Java-объект. Для тестирования подобных запросов, которые нельзя выполнить напрямую через браузер, рекомендуется использовать инструменты типа Postman или встроенные HTTP-клиенты в IDE, например, специализированные .http файлы в IntelliJ IDEA.

🚀 Улучшение API: Валидация и переход к базам данных 1:23:36

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

Валидация данных с помощью Bean Validation API 1:23:36

Для предотвращения попадания некорректных данных в систему в Spring Boot используется Bean Validation API. Этот подход позволяет декларативно задавать ограничения для полей наших моделей.

Первым делом необходимо добавить зависимость spring-boot-starter-validation в файл pom.xml. После обновления зависимостей в IDE мы можем использовать стандартные аннотации над полями класса или записи (record):

Чтобы активировать эти правила в контроллере, достаточно добавить аннотацию @Valid перед аргументом метода, принимающего данные. Если переданный объект нарушает правила, Spring автоматически прервет выполнение метода и вернет клиенту ошибку 400 Bad Request, избавив нас от написания ручных проверок. При наличии spring-boot-devtools подробности ошибок валидации будут автоматически логироваться в консоль, что значительно упрощает отладку.


Подключение базы данных H2 и JDBC Client 1:33:20

До этого момента мы использовали in-memory представление данных, которое обнулялось при каждом перезапуске приложения. Теперь мы переходим к интеграции с полноценной базой данных.

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

  1. Добавление зависимостей: Необходимо подключить h2 (runtime-зависимость) и spring-boot-starter-jdbc для работы с JDBC API.
  2. Настройка подключения: Spring автоматически настраивает соединение. Благодаря DevTools, мы получаем доступ к H2 Console — веб-интерфейсу, позволяющему визуально управлять содержимым базы данных через браузер.
  3. Именование БД: Мы можем управлять именем базы данных через application.properties, установив spring.datasource.name=Runners, что позволяет получить предсказуемый JDBC URL для подключения.

Автоматизация схемы через schema.sql 1:39:35

Поскольку in-memory база данных очищается при каждом запуске, Spring предоставляет механизм автоматической инициализации схемы. Достаточно создать файл schema.sql в директории src/main/resources. При старте приложения Spring обнаружит этот файл и выполнит содержащиеся в нем SQL-инструкции для создания таблиц, соответствующих нашей модели данных. Это гарантирует, что структура БД всегда будет синхронизирована с кодом приложения.

🛠️ Переход к базам данных: Эра JdbcClient и автоматизация данных 1:40:27

От JdbcTemplate к современному JdbcClient 1:42:05

Долгое время разработчики в экосистеме Spring использовали JdbcTemplate для упрощения взаимодействия с базами данных поверх стандартного JDBC API. Однако с выходом Spring Boot 3.2 ситуация качественно изменилась: появился JdbcClient. Этот инструмент предлагает разработчикам интуитивный беглый (fluent) API, который делает код работы с базами данных более читаемым и лаконичным, сохраняя при этом гибкость нативных SQL-запросов.

Интеграция JdbcClient в существующий репозиторий RunRepository наглядно демонстрирует мощь механизмов внедрения зависимостей (Dependency Injection) в Spring. Благодаря наличию JDBC API в classpath, Spring Boot автоматически конфигурирует экземпляр JdbcClient и помещает его в контекст приложения. Разработчику остаётся лишь получить его через инжекцию в конструктор, избегая ручного создания объектов.

Первым шагом в замене старого хранилища в оперативной памяти становится написание метода findAll(). С использованием JdbcClient этот процесс выглядит как лаконичная цепочка вызовов: определение SQL-скрипта SELECT * FROM run, выполнение запроса и автоматический маппинг результатов в Java-рекорды.

Полноценный CRUD на уровне базы данных 1:47:15

Для полноценной замены коллекции в оперативной памяти реальной базой данных требуется реализовать весь спектр CRUD-операций. При поиске сущности по идентификатору через метод findById используется механизм именованных параметров. JdbcClient позволяет гибко связывать переменные из сигнатуры метода с SQL-запросом, возвращая безопасный контейнер Optional<Run> на случай, если запись отсутствует в таблице.

Что касается модификации данных — создания, обновления и удаления записей — все эти операции выполняются через метод update() в JdbcClient. Важной архитектурной деталью здесь является контроль целостности данных: метод возвращает количество изменённых строк. Это позволяет приложению программно удостовериться, что в результате транзакции пострадала ровно одна ожидаемая запись, и выдать ошибку, если это не так. Проверка работоспособности обновлённого контроллера через встроенную консоль H2 подтверждает, что слой абстракции работает прозрачно.

Программный бутстрап и пакетная загрузка через JSON 1:50:38

Использование статичных файлов инициализации вроде data.sql удобно на старте, но реальные задачи часто требуют программного управления процессом первичной загрузки данных (bootstrap). Идеальным инструментом для этого выступает CommandLineRunner — функциональный интерфейс Spring, метод run которого автоматически выполняется сразу после полного запуска контекста приложения. Поскольку это функциональный интерфейс, в простых сценариях его можно объявить в виде Lambda-выражения прямо внутри конфигурационного бина.

Однако для комплексных задач инициализации эффективнее выносить логику в отдельные компоненты. Пример тому — класс RunJsonDataLoader, помеченный аннотацией @Component. Его задача — прочитать пакет из 10 готовых пробежек из файла runs.json, десериализовать их с помощью встроенного ObjectMapper в промежуточный Java-рекорд и массово сохранить в базу данных. Такой подход снабжён проверкой: загрузка происходит только в том случае, если текущий счётчик записей в таблице равен нулю.

Подготовка к продакшен-стеку: Прощай, H2 2:00:09

Встроенная база данных H2 великолепно подходит для быстрой итерации и локальных тестов, но реальный продакшен требует промышленного решения. В качестве такого стандарта выступает СУБД PostgreSQL. С точки зрения написанного Java-кода переход на новую базу практически незаметен, что доказывает зрелость абстракций Spring. Основные изменения затрагивают конфигурацию окружения: необходимо переопределить URL подключения, имя пользователя и пароль.

Стоит учитывать нюанс: по умолчанию Spring Boot автоматически выполняет инициализирующий скрипт schema.sql только для встроенных баз данных (таких как H2). При переходе на внешнюю СУБД этот запуск необходимо форсировать, установив свойство spring.sql.init.mode в положение always.

Чтобы избавить разработчика от необходимости вручную скачивать и настраивать PostgreSQL в операционной системе, применяется контейнеризация через Docker. Начиная со Spring Boot 3.1, в экосистему был добавлен модуль поддержки Docker Compose, который радикально упрощает локальную разработку.

Для перехода на полноценный стек в проекте выполняются следующие шаги:

🐳 Управление данными с PostgreSQL и Docker 2:05:33

Переход на использование PostgreSQL в связке с Docker Compose позволяет стандартизировать среду разработки для всей команды. Вместо того чтобы полагаться на случайные параметры контейнера, разработчики могут явно задать версию образа PostgreSQL в конфигурационном файле, обеспечивая единообразие инфраструктуры.

При настройке docker-compose.yml важно корректно определить сопоставление портов. Если указать только порт контейнера (5432), Docker выделит динамический порт на локальной машине, что затруднит подключение внешних инструментов. Явное указание маппинга «5432:5432» позволяет стабильно подключаться к базе данных через привычные инструменты управления, такие как база данных в IntelliJ IDEA, используя localhost:5432.

Spring Boot существенно упрощает работу с конфигурациями через механизм ConnectionDetails. Разработчикам не нужно дублировать параметры подключения (имя пользователя, пароль, URL базы данных) в файле application.properties, если они уже описаны в Docker Compose. Среда выполнения автоматически считывает эти детали и внедряет их в соответствующие свойства, что делает инфраструктуру проекта чище и избавляет от избыточного кода.

🛠 Spring Data JDBC: автоматизация и гибкость 2:10:02

После перехода с базовых решений (ранее в разговоре упоминались JDBC-клиент и H2), проект переходит к использованию Spring Data JDBC. Этот проект в составе экосистемы Spring Data предоставляет мощные абстракции, позволяя минимизировать рутинную работу с CRUD-операциями.

Ключевые преимущества Spring Data JDBC:

Для начала работы достаточно добавить зависимость spring-boot-starter-data-jdbc в pom.xml. Затем, пометив поле идентификатора сущности аннотацией @Id и, при необходимости, добавив поле версии (аннотация @Version) для отслеживания состояния строк, разработчик получает полноценную систему работы с данными без написания шаблонного кода. В случаях, когда стандартных возможностей недостаточно, всегда можно прибегнуть к аннотации @Query для написания собственных SQL-выражений, сохраняя при этом все преимущества репозиторного подхода.

🌐 Взаимодействие с внешними сервисами: Rest Client и HTTP-интерфейсы 2:30:37

Современные приложения на базе Spring Boot 3 всё чаще требуют интеграции с внешними микросервисами. Для реализации такого взаимодействия в экосистеме Spring произошла эволюция инструментов, направленная на упрощение кода и отказ от избыточного boilerplate.

Использование Rest Client 2:31:37

Одним из наиболее эффективных инструментов для синхронного общения между микросервисами является RestClient, представленный в Spring Framework 6.1 (и включенный в Spring Boot 3.2). По своей сути это интерфейс, предлагающий современный, плавный (fluent) API, который значительно выигрывает по читаемости у классического RestTemplate.

Основные аспекты работы с RestClient:

HTTP-интерфейсы в Spring 2:41:03

Вершиной абстракции для вызова внешних сервисов в Spring являются HTTP-интерфейсы. Этот подход позволяет полностью отказаться от написания кода самого HTTP-клиента, превращая процесс вызова внешнего API в работу с обычным Java-интерфейсом.

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

🧪 Экосистема тестирования в Spring Boot: от изолированных unit-тестов до срезов контекста 2:55:38

Настоящие unit-тесты: проверка бизнес-логики без участия Spring 2:56:19

Когда речь заходит о тестировании компонентов, не зависящих от внешней инфраструктуры, вовлекать полноценный фреймворк Spring нет необходимости. В качестве примера рассматривается класс InMemoryRunRepository, который хранит состояние внутри обычной коллекции в оперативной памяти. Для создания теста используется встроенный функционал среды разработки IntelliJ IDEA, которая автоматически генерирует правильную структуру пакетов и имя класса. Фреймворк JUnit 5 по умолчанию ищет тестовые классы, названия которых заканчиваются на Test или Tests.

Такой тест является «чистым» unit-тестом, поскольку в нем отсутствуют какие-либо аннотации Spring. Для инициализации тестовой среды применяется метод с аннотацией @BeforeEach, выполняющий предварительную настройку окружения перед каждым тестовым запуском. Поскольку Spring не управляет жизненным циклом компонента в таком сценарии, методы жизненного цикла (такие как @PostConstruct) не вызываются автоматически, и тестовые данные приходится добавлять в коллекцию вручную.

Типичный тест, например shouldFindAllRuns, вызывает метод репозитория и проверяет результат с помощью утверждений (assertions). В JUnit 5 хорошей практикой является передача третьего необязательного аргумента в метод утверждения — текстового сообщения, которое выводится в случае падения теста (например, «should have returned 2 runs»). Спикер также упоминает подход TDD (Test-Driven Development), при котором разработчик сначала пишет падающий тест, а затем реализует код метода для его успешного прохождения. Набор простых тестов позволяет быстро покрыть всю базовую функциональность компонента, включая поиск по валидному или невалидному ID и создание новых записей.

Тестирование срезов данных: эффективное использование аннотации @JdbcTest 3:02:01

Переходя к тестированию компонентов, работающих с базой данных, таких как JdbcRunRepository, подход меняется. Здесь уже нельзя обойтись простым unit-тестом, так как коду требуется инфраструктура Spring. Использование универсальной аннотации @SpringBootTest на начальном этапе кажется удобным, однако по мере роста приложения это существенно замедляет сборку, поскольку аннотация полностью загружает весь контекст приложения со всеми классами системы.

Для оптимизации процесса в Spring Boot существуют так называемые «тесты срезов» (Slice Tests), загружающие только необходимую часть контекста. Для слоя доступа к данным используется аннотация @JdbcTest.

Согласно официальной документации, @JdbcTest обладает следующими особенностями:

Ранее в разговоре авторы касались интеграции PostgreSQL через Docker и использования базы данных H2, однако для локальных тестов спикер предпочитает подход с Testcontainers, чтобы окружение было максимально приближено к продакшену. Если разработчик хочет отказаться от автоматической подмены базы данных на встраиваемую и использовать уже запущенную внешнюю БД, применяется аннотация @AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE). При написании таких тестов внедрение зависимостей через поле с помощью @Autowired считается допустимым, поскольку разработчик не тестирует сам тестовый класс. Если нужный бин не подтягивается автоматически из-за специфики работы среза, его необходимо импортировать явно с помощью аннотации @Import.

Изолированная проверка веб-слоя с помощью @WebMvcTest и MockMvc 3:09:54

Следующим важным этапом является тестирование веб-компонентов, а именно контроллера RunController. Разработчик может протестировать его с двух ракурсов: как изолированный юнит-тест ввода-вывода веб-слоя и как полноценный интеграционный сценарий. Для изолированного тестирования применяется специализированный срез @WebMvcTest(RunController.class).

Эта аннотация автоматически настраивает инструмент MockMvc, который является главной точкой входа для поддержки серверного тестирования Spring MVC. С помощью MockMvc можно имитировать HTTP-запросы в изолированном окружении, отправляя, например, GET-запросы на эндпоинт /api/runs и проверяя возвращаемый статус ответа. Также в контекст внедряется компонент ObjectMapper из библиотеки Jackson, необходимый для сериализации и десериализации JSON-данных.

Поскольку контроллер зависит от репозитория, выполняющего запросы к базе данных, в изолированном тесте эту внешнюю зависимость необходимо подменить. Для этого используется аннотация @MockBean из фреймворка Mockito, создающая фиктивный объект вместо реального бина. Описание поведения заглушки задается с помощью конструкции when(...).thenReturn(...).

Для верификации структуры и содержимого JSON-ответа применяется библиотека JsonPath, которая позволяет строить декларативные выражения и проверять, к примеру, размер возвращаемого массива данных. В ходе написания тестов для методов получения, создания и удаления данных разработчик верифицирует корректность HTTP-статусов и точность возвращаемой информации.

Переход к сквозному тестированию: конфигурация интеграционной среды 3:18:29

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

Чтобы избежать конфликтов портов в тестовом окружении, свойство webEnvironment настраивается на использование случайного порта: @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT). Для получения динамически назначенного номера порта внутри тестового класса используется аннотация @LocalServerPort, которая внедряет значение в целочисленную переменную.

Ранее в разговоре упоминалось использование Rest Client для выполнения HTTP-запросов. В методе предварительной настройки интеграционного теста @BeforeEach создается экземпляр RestClient с базовым URL, указывающим на локальный хост и динамический порт (http://localhost: + port). Это позволяет выполнять настоящие сетевые вызовы к развернутому тестовому веб-серверу для проверки сквозных сценариев работы приложения.

🏁 Проверка приложения в бою: сквозное интеграционное тестирование и финал курса 200:51

Сквозная проверка взаимодействия: от контроллера до базы данных 200:51

Финальный этап разработки любого серьезного веб-приложения неизбежно упирается в проверку того, как все спроектированные компоненты работают в связке. Автор переходит к демонстрации полноценного интеграционного теста, который призван подтвердить корректность всей цепочки вызовов внутри приложения на Spring Boot 3. В рамках этого теста выполняется реальный HTTP-запрос к эндпоинту контроллера, который перенаправляет вызов в репозиторий, а тот, в свою очередь, обращается к базе данных. Получив ответ от БД, приложение возвращает данные обратно по цепочке слоев.

Для верификации корректности работы автор извлекает список объектов и использует классическое утверждение assertEquals, ожидая увидеть ровно 10 записей, поскольку именно такое количество тестовых данных было предварительно загружено в базу. Подобный подход позволяет убедиться в отсутствии скрытых багов на стыке слоев архитектуры. Разработчик подчеркивает, что хотя в рамках данного демонстрационного примера рассматривается только сценарий получения полного списка объектов, в реальной практике абсолютно необходимо покрывать аналогичными интеграционными тестами и все остальные CRUD-операции: поиск по идентификатору (find by ID), создание (POST), обновление (PUT) и удаление (DELETE) записей. Ранее в разговоре они детально касались общих стратегий тестирования в Spring Boot, однако именно сквозная интеграция с реальной БД дает стопроцентную уверенность в стабильности системы перед деплоем.

Изолированное тестирование внешних интеграций с помощью @RestClientTest 202:17

Особый интерес представляет тестирование компонентов, которые взаимодействуют со сторонними API. Автор предлагает детально разобрать специализированный инструмент — аннотацию @RestClientTest. Этот механизм разработан специально для тестирования REST-клиентов в Spring и позволяет существенно оптимизировать процесс, не загружая полное тяжеловесное окружение приложения. При использовании @RestClientTest Spring Boot отключает полную автоконфигурацию контекста, фокусируясь исключительно на тех бинах, которые необходимы для работы RestTemplateBuilder или RestClient.Builder. В контекст подгружаются только критически важные компоненты, такие как конфигурации для работы с JSON (Jackson или Gson), тогда как стандартные пользовательские бины бизнес-логики игнорируются, что значительно ускоряет запуск тестов.

Главное преимущество этой аннотации заключается в том, что разработчик автоматически получает в свое распоряжение настроенный инструмент MockRestServiceServer. Этот виртуальный сервер позволяет элегантно имитировать ответы от внешних ресурсов. Вместо того чтобы отправлять реальные сетевые запросы к публичным сервисам, таким как JSONPlaceholder, тест перехватывает исходящий вызов и возвращает заранее подготовленный JSON-ответ со статусом успешного выполнения. Написание такого теста строится по популярному шаблону Given-When-Then («Дано — Когда — Тогда»), что делает структуру сценария прозрачной и легко читаемой.

В процессе живой демонстрации кода автор сталкивается с классической проблемой типов данных, которая часто возникает при интеграции: координатные поля широты и долготы (latitude и longitude) в тестовом объекте адреса были ошибочно объявлены как строки, в то время как ожидался тип double. Это приводит к падению сборки с ошибкой несовместимости типов. После оперативного исправления модели данных автор использует мощный инструмент утверждений assertAll для одновременной проверки целого набора вложенных полей: имени пользователя, email, адреса, телефона, веб-сайта и компании. С третьей попытки все тесты успешно проходят, подтверждая надежность интеграционного решения.

Философия разработки и глобальные ресурсы экосистемы Spring 206:57

Завершая практический блок, посвященный тестированию, инструктор делится важной методологической рекомендацией для начинающих разработчиков. На этапе освоения Spring Boot первоочередной задачей является понимание общей механики и сборка работающего прототипа. Однако как только базовые навыки закреплены, тестирование должно стать неотъемлемой частью повседневной рутины. Более того, автор настоятельно рекомендует практиковать подход TDD (Test-Driven Development), при котором тесты для нового контроллера пишутся еще до реализации самого контроллера. Постоянное повторение этого цикла позволяет довести навык написания качественных тестов до полного автоматизма.

Этот масштабный курс подходит к концу, и автор вырачает искреннюю благодарность платформе freeCodeCamp.org за предоставленную площадку, а также всем зрителям за проявленный интерес и упорство. Созданный для курса GitHub-репозиторий задуман как постоянно обновляемый, живой документ. Автор планирует поддерживать его, своевременно обрабатывать поступающие тикеты (issues) и открывать дискуссии для разбора сложных вопросов.

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

Выразив признательность своему работодателю Broadcom и всей команде разработки Spring за поддержку в создании этого туториала, автор завершает обучение традиционным и вдохновляющим напутствием: «Happy coding, friends!».

💬 Цитаты

«Я хочу, чтобы вы были продуктивны. Используйте те инструменты, которые позволяют вам достичь этой продуктивности, будь то хоть блокнот или терминал с Vim.»

«The controller is the traffic cop of our system it is going to take in a request it is going to figure out what we need to do with it.»

«In 3.2 we got something called the jdbc client this simplified that abstraction and really give us this nice fluent API.»

Инструктор 42:18

«Для меня одно из главных преимуществ Rest Client перед RestTemplate — это отсутствие огромного количества перегруженных методов.»

«once you get the mechanics down try to make it a focus of okay I just wrote this controller what kind of test can I write for it.»

👥 Спикер
🔗 Упомянутые сайты и проекты
📖 Термины
IoC (Inversion of Control)
Принцип проектирования, при котором управление потоком выполнения программы делегируется фреймворку.
Java Records
Лаконичный способ объявления неизменяемых классов данных, появившийся в современных версиях Java.
JdbcClient
Упрощенная и удобная абстракция для работы с базами данных, представленная в Spring Boot 3.2.
Технологии и IT Spring Boot 3 Java REST API PostgreSQL Docker Compose