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, то думка проста: сервіс має запускатися на вашій машині, у спільному dev-оточенні команди та в «умовному проді» (навіть якщо поки що це просто запуск jar), причому без правок Java-коду. Відмінності мають бути зрозумілими, мінімальними й передбачуваними — щоб будь-яка людина могла відкрити конфіги й сказати: “ага, ось це local, ось це dev, а ось це prod”.

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

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

Важливо, що це не просто “три файли”, а три ролі.

local — це запуск на вашій машині. Тут допустимі зручності: інший порт (щоб не конфліктувати з чимось ще), більш «балакучі» налаштування (у розумних межах), можливо, трохи зручніша поведінка для ручної перевірки. Це профіль “щоб мені було зручно”.

dev — це спільна development-конфігурація, максимально схожа на командну базову. Це не “мій ноутбук”, а “наше спільне середовище”. Якщо local можна змінювати під себе, то dev має бути стабільним і відтворюваним для всіх.

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

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

Профіль Хто запускає Головна мета Що зазвичай відрізняється Що не повинно туди потрапляти
local конкретний розробник зручний запуск на своїй машині порт, зручні прапорці, локальні значення командні/продові секрети, «загальні налаштування проєкту»
dev команда / спільний стенд спільна базова конфігурація для розробки налаштування, що допомагають у 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 зручно тимчасово бачити й неопубліковані — щоб тестувати дані та фільтрацію. Це не “feature flag на п’ять хвилин”, а доволі стабільна різниця: у проді ми обережні, локально — досліджуємо й перевіряємо.

А ось що краще залишити спільним у базовому 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 — це зручний псевдонім, а не обов’язкова частина проєктної бази. Якщо вам вистачає явного local або dev, група взагалі не потрібна. Вона корисна лише там, де одна й та сама комбінація профілів повторюється знову й знову.

У Spring Boot для цього використовується spring.profiles.group.*. З погляду mental model це просто “аліас” (псевдонім). Жодного окремого всесвіту: правила last wins нікуди не зникають, порядок профілів усе ще важливий, а конфліктні налаштування все ще конфліктуватимуть — просто ви тепер вмикаєте набір профілів одним словом.

І ось тут дуже важливо не перетворити групи на спосіб приховати проблеми. Якщо ви робите групу chaos = [dev, local, prod], то це не стратегія оточень, а самознищення із затримкою. Група хороша, коли вона описує стійке й зрозуміле поєднання. Наприклад: “у нашій локальній розробці ми хочемо взяти спільну dev-базу, а поверх неї накласти особисті локальні відмінності”. У такому сценарії група справді економить сили й зменшує кількість помилок під час запуску.

У YAML це виглядає охайно й читабельно:

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

Зверніть увагу на порядок: ми перелічили dev, а потім local. Це зроблено не заради краси, а заради змісту: якщо один і той самий ключ є і там, і там, то local має право перекривати dev, бо local є більш специфічним. Інакше ви отримаєте “локальний профіль, який локально нічого не вирішує”, а це трохи прикро.

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

flowchart TD
    A["spring.profiles.active=full-dev"] --> B["full-dev (група)"]
    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 тут — саме необов’язковий зручний псевдонім: базова схема local/dev/prod і без нього вже робоча. Група потрібна лише для того, щоб однією командою вмикати спільну dev-базу й локальні відмінності поверх неї. Коли ролі профілів уже узгоджені, далі залишається лише акуратно зібрати YAML без дублювань.

Базовий application.yaml тримаємо коротким і спільним:

spring:
  application:
    # Ім'я застосунку зазвичай однакове в усіх оточеннях
    name: "catalog-service"

app:
  catalog:
    # Базовий title — спільне значення за замовчуванням
    title: "Course Catalog"
    # Базова політика: у prod це значення має бути безпечним
    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() зробити найпростіший знімок стану. Приклад нижче — саме фрагмент, який можна вставити всередину run() (решта класу у вас уже є):

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

// environment — це Environment, який ви отримуєте через DI (наприклад, у конструкторі/полі)
System.out.println("профілі=" + 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.

Коментарі
ЩОБ ПОДИВИТИСЯ ВСІ КОМЕНТАРІ АБО ЗАЛИШИТИ КОМЕНТАР,
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ