JavaRush /Курсы /Spring Boot /Окружения: local, ...

Окружения: local, dev, prod

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

1. Профили и стратегия окружений

Когда начинаешь работать с профилями, очень легко попасть в ловушку: кажется, что раз есть механизм local/dev/prod, то всё автоматически станет аккуратно и красиво. На практике профили — это как кухонные ножи: вещь полезная, но если разложить их по всей квартире, жить становится тревожно. Поэтому нам нужна environment strategy — договорённость, какие профили существуют, зачем они существуют и какие различия им разрешены.

Самая частая причина хаоса — отсутствие правил. Один разработчик делает профиль oleg-laptop, другой — dev2, третий — prod-new, а четвёртый просто пишет application.properties, потому что «так привычнее». В итоге приложение стартует, но никто не может уверенно сказать, почему оно стартует именно так. Стратегия окружений — это способ заранее ответить на вопрос: “какие различия между запусками считаются нормальными, а какие — признак беспорядка?”

Если связать это с нашим catalog-service, то мысль простая: сервис должен запускаться на вашей машине, в общей дев‑среде команды и в «условном проде» (пусть даже пока это просто запуск jar), причём без правок Java-кода. Различия должны быть понятными, минимальными и предсказуемыми — чтобы любой человек мог открыть конфиги и сказать: “ага, вот это local, вот это dev, а вот это prod”.

2. База: local, dev, prod

Когда говорят “окружения”, мозг сразу рисует страшную матрицу: dev, dev2, dev-eu, staging, preprod, prod, prod-hotfix, и ещё сверху “профиль для понедельника”. Для учебного проекта (и для многих реальных сервисов на старте) такая матрица не нужна. Нам нужен минимальный, но устойчивый набор, который покрывает типичные сценарии и не создаёт лишней когнитивной нагрузки. И обычно это три профиля: local, dev, prod.

Важно, что это не просто “три файла”, а три роли.

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

dev — это общая development‑конфигурация, максимально похожая на командный baseline. Это не “мой ноутбук”, а “наша общая среда”. Если local можно менять под себя, то dev должен быть стабильным и воспроизводимым для всех.

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

Чтобы зафиксировать смысл без долгих философских лекций, вот компактная таблица. Она не заменяет мышление, но помогает быстро проверить: “мы точно туда складываем настройки?”

Профиль Кто запускает Главная цель Что обычно отличается Что не должно туда попадать
local конкретный разработчик удобный запуск на своей машине порт, удобные флаги, локальные значения командные/продовые секреты, «общие настройки проекта»
dev команда / общий стенд общий baseline для разработки настройки, помогающие dev‑работе, но не личные “индивидуальный порт Пети”, случайные эксперименты
prod реальный деплой / jar-run строгость и безопасность по умолчанию минимальные и безопасные значения dev‑удобства, «временно отключим фильтр», “откроем всё наружу”

Если коротко, local — это “удобно”, dev — это “общо”, prod — это “строго”. И это уже половина успеха, потому что многие проблемы профилей начинаются ровно там, где люди путают эти роли.

3. catalog-service: что различать по окружениям

Когда у вас уже есть local/dev/prod, следующий соблазн — начать “раскрашивать” профилями вообще всё. На этом этапе полезно задать себе один простой вопрос: это реально разница окружений или просто параметр, который иногда хочется поменять? Если это параметр, то часто достаточно override через env var или CLI, а профиль не нужен.

В нашем catalog-service сейчас (на стадии до @ConfigurationProperties) у нас есть несколько свойств, которые очень естественно воспринимаются как средовые отличия. Например, server.port — в local часто хочется запускаться не на 8080, потому что на 8080 уже сидит что-нибудь (или прошлый “hello world” у вас забыл умереть). Ещё пример — app.catalog.title: в local полезно видеть в заголовке “(local)”, чтобы по браузеру/логам не путаться, что именно запущено.

Есть и более «поведенческие» настройки. Например, app.catalog.default-published-only. В prod логично показывать только опубликованные курсы по умолчанию. В local и иногда в dev удобно временно видеть и неопубликованные — чтобы тестировать данные и фильтрацию. Это не “фича-флаг на пять минут”, а довольно стабильная разница: в проде мы осторожны, локально — исследуем и проверяем.

А вот что лучше оставить общим в базовом application.yaml: spring.application.name, структуру app.catalog.* и значения по умолчанию, которые являются нормой проекта. Общие значения — это фундамент, на котором стоят все профили. Если фундамент дублировать в трёх местах, вы рано или поздно забудете обновить один из файлов и получите классический баг “почему в dev одно, а в prod другое?” (спойлер: потому что вы сами себе это устроили).

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

4. Profile groups без магии

Профили удобны, но руками писать --spring.profiles.active=dev,local каждый раз — удовольствие примерно как вводить пароль от Wi‑Fi на телевизоре пультом. Вот здесь и появляются profile groups. Они позволяют сказать: “когда активен профиль X, автоматически активируй ещё вот эти профили”. То есть вы задаёте псевдопрофиль, который включает набор реальных.

Сразу важная граница: group — это convenience alias, а не обязательная часть проектного baseline. Если вам хватает явного local или dev, группа вообще не нужна. Она полезна только там, где одна и та же комбинация профилей повторяется снова и снова.

В Spring Boot для этого используется spring.profiles.group.*. С точки зрения mental model это просто “алиас” (псевдоним). Никакой отдельной вселенной: правила last wins никуда не исчезают, порядок профилей всё ещё важен, а конфликтующие настройки всё ещё будут конфликтовать — просто вы теперь включаете набор профилей одним словом.

И вот здесь очень важно не превратить группы в способ спрятать проблемы. Если вы делаете группу chaos = [dev, local, prod], то это не стратегия окружений, а самоуничтожение с задержкой. Группа хороша, когда она описывает устойчивое и понятное сочетание. Например: “в нашей локальной разработке мы хотим взять общий dev‑baseline, а поверх него наложить личные локальные отличия”. В таком сценарии группа действительно экономит силы и снижает количество ошибок при запуске.

В YAML это выглядит аккуратно и читаемо:

spring:
  profiles:
    group:
      # Алиас: одним именем включаем набор профилей
      full-dev:
        # Порядок важен: более общий профиль раньше
        - dev
        # Более специфичный профиль последним, чтобы он мог перекрывать значения
        - local

Обратите внимание на порядок: мы перечислили dev, а потом local. Это сделано не ради красоты, а ради смысла: если один и тот же ключ есть и там, и там, то local должен иметь право перекрыть dev, потому что local более специфичный. Иначе вы получите “локальный профиль, который локально ничего не решает”, а это слегка обидно.

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

flowchart TD
    A["spring.profiles.active=full-dev"] --> B["full-dev (group)"]
    B --> C["dev"]
    B --> D["local"]
    C --> E["application-dev.yaml"]
    D --> F["application-local.yaml"]
    G["application.yaml (base)"] --> E
    G --> F

То есть full-dev — это не отдельный мир. Это команда: “включи dev и local, а дальше Boot сделает обычную загрузку base + profile overrides”.

5. catalog-service: группа full-dev

Давайте соберём небольшой, но жизненный сценарий. full-dev здесь — именно optional convenience alias: базовая схема local/dev/prod и без него уже рабочая. Группа нужна только затем, чтобы одной командой включать общий dev‑baseline и локальные отличия поверх него. Когда роли профилей уже договорены, дальше остаётся только аккуратно собрать YAML без дублей.

Базовый application.yaml держим коротким и общим:

spring:
  application:
    # Имя приложения обычно одинаковое во всех окружениях
    name: "catalog-service"

app:
  catalog:
    # Базовый title — общее значение по умолчанию
    title: "Course Catalog"
    # Базовая политика: в проде это значение должно быть безопасным
    default-published-only: true

Локальные отличия — порт и более явный title:

# application-local.yaml
server:
  # Локально часто нужно уйти с 8080, чтобы не конфликтовать с другими сервисами
  port: 8081

app:
  catalog:
    # Пометка (local), чтобы не путаться по браузеру и логам
    title: "Course Catalog (local)"

dev-отличия — например, тоже title, но без личного порта (потому что порт в dev должен быть командно‑предсказуемым):

# application-dev.yaml
app:
  catalog:
    # Пометка (dev), чтобы по логам/метрикам было видно, где вы находитесь
    title: "Course Catalog (dev)"
    # В dev удобно видеть и неопубликованные данные при проверках
    default-published-only: false

prod обычно вообще минимален: он не должен копировать базовый файл, а должен фиксировать строгие отличия, если они есть. В нашем примере можно даже оставить application-prod.yaml пустым, но иногда полезно явно продублировать “строгое значение”, чтобы оно читалось как policy:

# application-prod.yaml
app:
  catalog:
    # Явно фиксируем policy для prod (даже если это совпадает с base)
    default-published-only: true

Если база уже держит safe default, такой продовый слой легко сократить до пустой дельты. Здесь он нужен только чтобы было видно саму идею строгого prod-поведения.

Теперь добавляем profile group в базовый application.yaml. Обычно её держат именно там, чтобы группа была частью “главной конфигурационной карты” приложения:

spring:
  profiles:
    group:
      # full-dev = dev baseline + локальные отличия поверх него
      full-dev:
        - dev
        - local

Как теперь запускать? Точно так же, как обычный профиль — просто в spring.profiles.active кладём имя группы:

# Запуск с активированной группой профилей (dev + local)
./gradlew bootRun --args="--spring.profiles.active=full-dev"

Если вам такой alias не нужен, ничего не ломается: обычные local, dev, prod уже полностью закрывают стратегию окружений.

А чтобы не гадать, “что реально включилось”, полезно добавить маленький диагностический вывод на старте. У нас уже есть StartupSummaryRunner, поэтому можно в его run() сделать простейший snapshot. Пример ниже — именно фрагмент, который можно вставить внутрь run() (остальной класс у вас уже есть):

import java.util.Arrays;
import org.springframework.core.env.Environment;

// environment — это Environment, который вы получаете через DI (например, в конструкторе/поле)
System.out.println("profiles=" + Arrays.toString(environment.getActiveProfiles()));
// Ключевые свойства лучше печатать с дефолтом, чтобы не получать null и лишние NPE в диагностике
System.out.println("title=" + environment.getProperty("app.catalog.title", "n/a"));

Если вы запустили с full-dev, вывод будет примерно таким (может отличаться порядком, но смысл тот же):

profiles=[full-dev, dev, local]
title=Course Catalog (local)

И это прекрасный момент истины. Мы видим, что group‑профиль тоже активен (он — “имя комбинации”), а также активны dev и local. И мы видим, что title в итоге взялся локальный, потому что local перекрыл dev. То есть правило last wins продолжает работать, просто теперь порядок профилей задаётся не руками в CLI, а вашей группой.

6. Профильная гигиена

Когда профили начинают приносить пользу, появляется новый соблазн: “а давайте добавим ещё один профиль… и ещё один… и вот этот тоже…”. В какой-то момент у вас уже 14 профилей, которые никто не понимает, и одна группа, которая включает половину из них, потому что “иначе не заводится”. Чтобы этого не случилось, полезно держать в голове несколько простых принципов — без пафоса, чисто чтобы не страдать.

Если отличие неустойчивое и живёт пару дней, профиль почти всегда лишний. В такие моменты лучше сделать override через env var или CLI и потом убрать. Профиль — это не “переключатель настроения”, а описание стабильной среды.

Если вы заметили, что application-dev.yaml стал таким же большим, как application.yaml, это сигнал тревоги. Значит, вы либо дублируете общее, либо у вас реально нет общего базового конфига (а это уже архитектурная проблема). Нормальный профиль‑файл выглядит коротко: он напоминает “патч”, а не “копию репозитория”.

Если вы используете profile groups, относитесь к ним как к алиасам. Они должны упрощать запуск и делать его менее ошибочным. Группа не должна быть “местом, где прячется конфликт настроек”. Если вам пришлось создать группу fix-it, чтобы “оно хоть как‑то запустилось”, лучше остановиться и разобраться, почему конфигурация конфликтует.

И ещё один принцип, который особенно важен в схеме local/dev/prod: по умолчанию полезно мыслить “один environment‑профиль за раз”. То есть prod не должен включаться вместе с dev. И если вы делаете группу, которая включает и dev, и local, относитесь к этому как к одному логическому “режиму разработки”, а не как к двум средам, которые случайно запустились вместе.

7. Типичные ошибки при работе с профилями

Ошибка №1: копирование конфигов между local, dev, prod.
Так обычно рождаются три одинаковых файла, которые через месяц перестают быть одинаковыми случайно и без вашего согласия. Хорошая стратегия — наоборот: базовый application.yaml хранит почти всё, а профильные файлы содержат маленькую дельту. Тогда изменения в проекте делаются в одном месте, и вы не играете в “найди отличие в трёх копиях”.

Ошибка №2: личные настройки разработчика попали в dev.
Это случается чаще, чем кажется. Кто-то поставил server.port: 8099 “потому что у меня занято”, закоммитил — и вся команда внезапно пытается понять, почему у них не открывается сервис на 8080. Личные отличия — это зона local. Профиль dev должен быть командным и максимально предсказуемым.

Ошибка №3: profile group используется как маскировка конфликтов.
Группы иногда начинают жить странной жизнью: “включи full-dev, иначе не работает”, а почему не работает — никто уже не помнит. Группа должна быть прозрачной: вы должны легко объяснить, какие профили она включает и почему именно такой порядок. Если объяснение превращается в легенду, значит стратегия окружений уже где-то дала трещину.

Ошибка №4: неправильный порядок профилей внутри группы.
Это особенно коварно, потому что всё “как будто работает”, но не так. Если в группе вы перечислили local, а потом dev, то dev‑значения будут перекрывать local‑значения. В результате вы изменяете что-то в application-local.yaml, а оно “не применяется”, и начинается гадание. Лекарство простое: более специфичный профиль должен идти последним, чтобы он мог перекрывать более общий.

Ошибка №5: ожидания вместо проверки.
Профили легко превращаются в игру “мне кажется, что активен dev”. Но Boot не читает мысли. Он читает property sources и собирает итоговую картину. Поэтому полезно иметь хоть один маленький диагностический вывод (через Environment.getActiveProfiles() и пару ключевых properties), чтобы видеть не “как задумано”, а “как реально получилось”. Это особенно спасает, когда профили начинают приходить извне через env vars или CLI.

1
Задача
Spring Boot, 15 уровень, 3 лекция
Недоступна
Простая стратегия окружений `local`, `dev`, `prod`
Простая стратегия окружений `local`, `dev`, `prod`
1
Задача
Spring Boot, 15 уровень, 3 лекция
Недоступна
Profile group `full-dev`
Profile group `full-dev`
Комментарии
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ