DevTools и feedback loop

Spring Boot
19 уровень , 0 лекция
Открыта

1. Проблема медленного feedback loop в Boot-проекте

Если вы когда-то писали консольные Java-приложения, то помните приятную иллюзию: «поменял строчку — запустил — мгновенно увидел результат». В Spring Boot это быстро исчезает. Даже маленький сервис запускает контекст, сканирует компоненты, применяет auto-configuration, поднимает embedded server — и у вас появляется пауза между правкой и результатом. Эта пауза и есть наш враг.

Давайте введём термин без пафоса, но по делу: feedback loop — это время от момента, когда вы внесли изменение, до момента, когда вы увидели эффект этого изменения. Причём «увидели» — это не обязательно «оно заработало», иногда это «оно упало, и я понял почему» (что, вообще-то, тоже отличный результат).

В Boot-проекте этот цикл обычно выглядит примерно так:

flowchart TD
  A["Вы поменяли код/конфиг"] --> B["Сборка (компиляция)"]
  B --> C["Старт Spring Boot"]
  C --> D["Поднялся контекст + сервер"]
  D --> E["Проверка: браузер / curl / тест"]
  E --> A

И вот что важно именно для обучения. Когда вы начинающий, у вас мозг ещё не «закешировал» поведение Spring Boot. Если между правкой и результатом проходит 10–15 секунд (или больше), вы успеваете: отвлечься, забыть, что поменяли, начать сомневаться, а не сломали ли вы вообще всё. Итог — вы меньше экспериментируете, и обучение становится вялым. Как говорится, «меньше правок — меньше ошибок», но и меньше понимания.

В catalog-service это особенно заметно, потому что мы часто меняем вещи, которые хочется проверять сразу. Вы поправили текст на landing page — хочется просто обновить страницу. Вы поменяли app.catalog.title в YAML — хочется увидеть новый заголовок. Вы подкрутили контроллер — хочется мгновенно получить новый JSON. Без ускорителя вы быстро начинаете жить в режиме «ну ладно, накоплю 5 правок, потом один раз перезапущу». А это почти всегда приводит к классике жанра: «не понимаю, какая из пяти правок всё сломала».

Сейчас это чувствуется особенно хорошо: у нас уже есть живой catalog-service с профилями, application-local.yaml и @ConfigurationProperties. Конфигурацией мы уже умеем управлять, но проверять каждую такую правку всё ещё долго. Значит, следующая боль не про YAML и не про binding, а про скорость локальной проверки изменений.

2. Spring Boot DevTools и режим dev-only

DevTools — это не «другая версия Spring Boot» и не волшебная палочка, которая делает “горячую перезагрузку как в кино”. Это просто библиотека (зависимость), которую вы добавляете в проект, и которая включает набор dev-only возможностей: ускоренный рестарт приложения при изменениях, более удобные дефолты для разработки, иногда LiveReload для фронтовых ресурсов и ещё несколько удобных мелочей.

Главная идея DevTools очень приземлённая: сделать локальную разработку быстрее, но при этом не менять архитектуру приложения. Ваши контроллеры, сервисы, репозитории, @ConfigurationProperties, профили и вся ваша аккуратная структура пакетов остаются теми же. DevTools не «чинит wiring», не «лечит broken config» и не заменяет понимание startup flow. Он просто помогает вам чаще получать обратную связь.

И вот тут принципиально важная граница курса (и вообще нормальной инженерной практики): DevTools должен быть dev-only. То есть он нужен на вашей машине, пока вы пишете код и учитесь, но он не должен становиться частью «обычного» runtime сервиса. Почему? Потому что dev-инструменты по определению вмешиваются в поведение запуска, могут поднимать дополнительные сервисы (например, LiveReload), меняют дефолтные настройки, и вообще живут с философией «пусть будет удобнее разработчику, даже если чуть менее похоже на production».

У DevTools есть ещё одна психологическая ловушка: если вы подключили его неправильно, вы можете случайно начать думать, что «так и должно быть». А потом внезапно окажется, что без DevTools приложение стартует иначе или медленнее, и вы начинаете ловить призраков. Поэтому сегодня наша цель — подключить DevTools максимально аккуратно и честно: как локальный ускоритель, который не загрязняет основной runtime.

3. DevTools в catalog-service: developmentOnly

В Gradle у нас есть очень полезная мысль: не все зависимости одинаковы. Одни нужны приложению всегда (implementation), другие — только на тестах (testImplementation), третьи — только во время разработки. DevTools относится к третьей группе, и Spring Boot Gradle Plugin как раз поддерживает это через конфигурацию developmentOnly.

В build.gradle.kts подключение выглядит так (и это тот случай, когда «короче = лучше», потому что меньше шансов случайно сделать не то):

dependencies {
    // DevTools подключаем только для локальной разработки,
    // чтобы не тащить его в обычный runtime приложения
    developmentOnly("org.springframework.boot:spring-boot-devtools")
}

Смысл здесь очень практический. DevTools будет доступен в локальном запуске (в частности, когда вы запускаете через Gradle), но при сборке executable jar он не должен уезжать внутрь артефакта и становиться частью runtime в чужих окружениях.

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

Как подключили DevTools Что получится Почему это (не)хорошо
implementation("...devtools") DevTools окажется в обычной runtime-зоне проекта Можно случайно «привезти удобства разработки» туда, где они не нужны
developmentOnly("...devtools") DevTools остаётся инструментом для локальной разработки Это честная граница: ускоряем разработку, не меняем основной runtime

Небольшая, но важная ремарка про IDE. Если вы запускаете приложение строго через Gradle (bootRun) — всё будет предсказуемо: developmentOnly попадёт в запуск. Если вы запускаете через кнопку Run в IDE (не через Gradle), поведение зависит от того, как IDE импортировала Gradle-конфигурации. Поэтому в учебном проекте полезно держать в голове простое правило: если вы не уверены, почему DevTools «не работает», попробуйте запуск через ./gradlew bootRun. Это не «правильнее», это просто более контролируемо.

Иногда вы можете встретить проект, где дополнительно явно расширяют classpath, чтобы IDE не «потеряла» dev-only зависимости. В нашем курсе мы стараемся не плодить лишнюю Gradle-церемонию без необходимости, но понимать сам принцип полезно:

configurations {
    // Если DevTools «не виден» при запуске из IDE, иногда помогает
    // добавить dev-only конфигурацию в runtimeClasspath
    named("runtimeClasspath") {
        // Spring Boot plugin обычно уже создаёт developmentOnly
        extendsFrom(named("developmentOnly").get())
    }
}

Если у вас DevTools ведёт себя странно именно при запуске из IDE, а через bootRun всё нормально, то вот эта настройка часто объясняет, «почему так». Но для обычного local-режима это не стартовая настройка: сначала достаточно developmentOnly(...) и локального профиля, а до classpath-тюнинга имеет смысл доходить только когда есть реальный симптом.

4. DevTools через local-профиль

Подключить зависимость — это половина дела. Вторая половина — не размазать dev-настройки по всему проекту, как кетчуп по белой футболке (выглядит эффектно, но потом неудобно объяснять). У нас уже есть чёткая strategy по окружениям: local, dev, prod. И логично, что DevTools относится именно к local.

Поэтому devtools-конфигурацию мы держим в application-local.yaml, а не в общем application.yaml. Так мы сохраняем главный принцип: общий конфиг остаётся «про приложение», а локальный — «про удобство разработки на вашей машине».

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

# src/main/resources/application-local.yaml
spring:
  devtools:
    restart:
      enabled: true # Явно фиксируем намерение: в local рестарт включён

Здесь есть нюанс: сам факт наличия DevTools на classpath обычно уже включает его возможности по умолчанию. Но явный флаг в local профиле даёт два бонуса. Во‑первых, это документирует намерение: «в local мы хотим DevTools». Во‑вторых, это позволяет быстро сравнивать поведение: переключили профиль — и вы точно понимаете, что изменилось.

На этом и стоит остановиться как на обычном local-режиме: DevTools подключён как developmentOnly, его настройки живут в application-local.yaml, restart включён. Всё остальное — уже тюнинг под конкретный симптом, а не обязательная часть проекта.

Важно не путать «локальный профиль» и «локальный компьютер». В реальных проектах local может быть очень разным у разных людей. Но в учебном контексте catalog-service это как раз хорошо: мы делаем поведение понятным и повторяемым. Если вам нужно, чтобы DevTools работал только у вас лично, а не у всех, тогда уже начинается разговор про локальные overrides, которые не коммитятся. В рамках курса мы держим всё проще и прозрачнее.

И ещё одна важная мысль: DevTools — это ускоритель. Он не должен превращаться в причину, почему проект запускается. Если вы заметили, что без DevTools сервис становится «неудобным», это не означает «DevTools обязателен». Это означает: «у нас слишком медленный startup / слишком много тяжёлой логики на старте / мы делаем что-то лишнее при поднятии контекста». Но разбор причин — это уже отдельная история. Сегодня мы просто подключаем ускоритель аккуратно.

5. Признаки активного DevTools

Когда вы подключили DevTools, хочется получить простой ответ на вопрос: «Он вообще включился? Или я просто добавил зависимость ради ощущения контроля над вселенной?» Хорошая новость: обычно DevTools себя выдаёт довольно честно — через startup logs.

Во время запуска в логах часто появляется строка о том, что активированы devtools defaults, и вы можете заметить характерное имя потока restartedMain (это один из самых узнаваемых «маркеров» devtools-режима). Примерно так:

INFO  ... DevToolsPropertyDefaultsPostProcessor : Devtools property defaults active!
INFO  ... RestartApplicationListener           : Restart enabled
INFO  ... CatalogServiceApplication            : Started CatalogServiceApplication

Не нужно запоминать эти строки как заклинание. Нам важно другое: вы получаете наблюдаемый признак, что приложение запущено в devtools-режиме. Если вы не видите ничего похожего, это ещё не катастрофа, но это сигнал проверить базовые вещи: точно ли зависимость подтянулась, точно ли запуск идёт тем способом, где developmentOnly попадает в classpath, точно ли активен local профиль, и не отключили ли вы restart где-то в конфиге.

Есть ещё один частый маркер: DevTools может поднимать LiveReload-сервер (и печатать про это строку). В нашем проекте мы не строим фронтенд и не делаем сложные браузерные workflow’ы, так что LiveReload для нас — приятная мелочь, но не цель. Тем не менее, если вы увидели в логах что-то вроде «LiveReload server is running…», то это ещё один признак, что DevTools действительно активен.

И последняя важная проверка — «проверка адекватностью». DevTools предназначен для локальной разработки и обычно не должен влиять на финальную форму приложения. Поэтому если вы запускаете сервис не как «проект в разработке», а как полностью собранный артефакт, devtools-режим, как правило, не включается. Это нормально: DevTools не должен становиться частью “истинного” поведения сервиса, иначе он перестаёт быть dev-only инструментом.

6. Типичные ошибки при подключении DevTools

Ошибка №1: подключать DevTools как обычную runtime-зависимость.
Самая частая проблема здесь не техническая, а концептуальная: инструмент для локальной разработки внезапно начинает жить рядом с основными зависимостями приложения. Даже если “ничего страшного не произошло”, сама модель уже неверная. DevTools должен оставаться dev-инструментом, а не частью обычного runtime.

Ошибка №2: складывать spring.devtools.* в общий application.yaml.
Когда в одном месте оказываются и бизнес-настройки (app.catalog.*), и чисто локальные удобства разработки, конфигурация быстро теряет читаемость. Через время уже непонятно, что влияет на поведение приложения, а что существует только ради комфорта разработчика. Для DevTools куда естественнее application-local.yaml или другой явно локальный слой конфигурации.

Ошибка №3: ожидать, что DevTools исправит сломанную конфигурацию.
DevTools ускоряет цикл перезапуска, но не лечит broken wiring, ошибки биндинга или падающий ApplicationContext. Если приложение не стартует без него, с ним оно тоже не “магически заработает”. Максимум — вы будете получать ту же ошибку быстрее, а это полезно, но это всё ещё не исправление проблемы.

Ошибка №4: запускать приложение так, что DevTools не попадает в classpath, и потом искать “мистику”.
Это типичная ловушка IDE-запуска: кажется, что всё должно работать так же, как при ./gradlew bootRun, но фактический classpath может отличаться. В результате DevTools вроде подключён в проекте, но в конкретном запуске не активен. Поэтому полезно держать bootRun как эталонный способ проверки: если DevTools должен быть виден, там это обычно проявляется наиболее предсказуемо.

Ошибка №5: воспринимать DevTools как обязательную часть приложения.
Когда разработчик слишком привыкает к ускоренному циклу, выключение DevTools начинает казаться “поломкой проекта”. Но DevTools — это не фундамент приложения, а всего лишь удобный ускоритель. Хорошая практика — уметь спокойно запускать и проверять приложение и с ним, и без него, чтобы понимать базовое поведение системы без дополнительных слоёв удобства.

1
Задача
Spring Boot, 19 уровень, 0 лекция
Недоступна
Локальный запуск с DevTools
Локальный запуск с DevTools
1
Задача
Spring Boot, 19 уровень, 0 лекция
Недоступна
DevTools в developmentOnly и typed configuration
DevTools в developmentOnly и typed configuration
Комментарии
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ