🍃 Розділ 5 · Питання #29

Що таке profiles in Spring

Один і той самий скомпільований JAR-архів додатка має працювати:


🟢 Junior Level

Profiles (профілі) у Spring Framework — це вбудований механізм сегментації конфігурації додатка, що дозволяє реєструвати різні біни та застосовувати різні властивості (application.properties / application.yml) залежно від поточного оточення запуску (наприклад: local, dev, stage, prod).

Навіщо потрібні профілі

Один і той самий скомпільований JAR-архів додатка має працювати:

  • На машині розробника (local): локальна база даних H2 або Docker-контейнер, увімкнено детальний лог DEBUG.
  • У тестовому контурі (test): моки зовнішніх платіжних шлюзів, ізольована база даних.
  • У бойовому оточенні (prod): кластер PostgreSQL з пулом HikariCP, захищені SSL-сертифікати, рівень логування WARN або INFO.

Базовий приклад із файлами налаштувань

Spring Boot автоматично підхоплює файл вигляду application-{profile}.yml на додаток до базового application.yml:

# application.yml (Загальні налаштування для всіх оточень)
server:
  port: 8080

---
# application-dev.yml (Лише для профілю dev)
spring:
  datasource:
    url: jdbc:h2:mem:devdb

---
# application-prod.yml (Лише для профілю prod)
spring:
  datasource:
    url: jdbc:postgresql://prod-db.internal:5432/orders

Як активувати профіль

  1. Через аргумент командного рядка під час запуску JAR:
    java -jar app.jar --spring.profiles.active=prod
    
  2. Через змінну оточення ОС (стандарт у Docker та Kubernetes):
    export SPRING_PROFILES_ACTIVE=prod
    
  3. Через системний параметр JVM:
    java -Dspring.profiles.active=prod -jar app.jar
    

🟡 Middle Level

Анотація @Profile над класами та @Bean-методами

Анотація @Profile вказує контейнеру створювати бін лише в тому випадку, якщо зазначений профіль активний:

package com.example.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;

@Configuration
public class NotificationConfig {

    @Bean
    @Profile("dev") // Створюється тільки при активному профілі dev
    public EmailSender mockEmailSender() {
        return new MockEmailSender(); // Виводить листи в консоль
    }

    @Bean
    @Profile("prod") // Створюється тільки в prod
    public EmailSender realEmailSender() {
        return new SmtpEmailSender(); // Відправляє реальні листи через поштовий сервер
    }
}

Складні логічні вирази в @Profile (Spring 5.1+)

Починаючи зі Spring 5.1, анотація @Profile підтримує оператори булевої логіки:

  • ! — логічне НЕ (NOT)
  • & — логічне І (AND)
  • | — логічне АБО (OR)
  • Дужки () для групування пріоритетів
@Component
@Profile("prod & !cloud") // Активний у prod, але тільки якщо ми НЕ в хмарі
public class OnPremiseMonitoringService { }

@Component
@Profile("dev | test") // Активний у dev АБО в test
public class TestDataSeeder { }

@Component
@Profile("prod & (aws | gcp)") // Складна складена умова
public class CloudStorageAdapter { }

Мультидокументні YAML-файли (Spring Boot 2.4+)

У сучасному Spring Boot усі профілі можна описати в одному файлі application.yml, розділяючи блоки роздільником --- та директивою spring.config.activate.on-profile:

server:
  port: 8080
spring:
  application:
    name: order-service

---
spring:
  config:
    activate:
      on-profile: dev
  datasource:
    url: jdbc:h2:mem:testdb

---
spring:
  config:
    activate:
      on-profile: prod
server:
  port: 443
spring:
  datasource:
    url: jdbc:postgresql://cluster.prod:5432/main

🔴 Senior Level

Групи профілів (Profile Groups)

У мікросервісних системах окремий сервіс може вимагати включення відразу кількох аспектів (кеш, метрики, інтеграція з Kafka). Замість переліку десятка профілів у рядку запуску (--spring.profiles.active=prod,metrics,kafka,redis) налаштовують Profile Groups:

# application.yml
spring:
  profiles:
    group:
      production:
        - prod
        - metrics
        - audit
        - redis-cache
      local-dev:
        - dev
        - h2-console
        - mock-external-apis

Тепер при передачі --spring.profiles.active=production Spring автоматично активує всі чотири пов’язані профілі.

Пріоритет перевизначення властивостей (Property Precedence)

Якщо одна й та сама властивість (наприклад, server.port) оголошена в кількох місцях, Spring Boot застосовує сувору ієрархію пріоритетів (від найвищого до найнижчого):

1. Аргументи командного рядка (--server.port=9090)
             ↓
2. Системні параметри JVM (-Dserver.port=9090)
             ↓
3. Змінні оточення ОС (SERVER_PORT=9090)
             ↓
4. Профіль-специфічні файли поза JAR (config/application-prod.yml)
             ↓
5. Профіль-специфічні файли всередині JAR (application-prod.yml)
             ↓
6. Дефолтні файли конфігурації поза JAR (config/application.yml)
             ↓
7. Дефолтні файли конфігурації всередині JAR (application.yml)

Правило: Налаштування з application-prod.yml завжди перевизначають співпадаючі властивості з базового application.yml.

Profiles vs Feature Flags: Архітектурна межа

Часта помилка архітекторів — спроба використовувати Spring Profiles для динамічного включення бізнес-фіч.

Критерій Spring Profiles Feature Flags (Toggles)
Що налаштовують Інфраструктура (БД, брокери, оточення) Бізнес-логіка (новий UI, алгоритм знижок)
Час перемикання Суворо під час старту JVM (вимагає рестарту) У рантаймі на льоту (без рестарту за секунду)
Інструменти Spring Environment, K8s ConfigMaps LaunchDarkly, Unleash, Togglz, Redis
Гранулярність На рівні всього додатка / Pod Для конкретних користувачів або відсотків трафіку (Canary)

Захист від запуску Prod без профілю (Fail-Fast Validation)

Якщо забути вказати профіль під час розгортання в Kubernetes, додаток запуститься з профілем default (який часто налаштований на тестову БД або H2 в пам’яті), що призведе до аварії.

Патерн обов’язкової валідації профілю:

package com.example.init;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.env.EnvironmentPostProcessor;
import org.springframework.core.env.ConfigurableEnvironment;
import java.util.Arrays;

public class MandatoryProfileEnvironmentPostProcessor implements EnvironmentPostProcessor {

    @Override
    public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
        String[] activeProfiles = environment.getActiveProfiles();
        
        // Якщо запущено не в тесті і жоден профіль не активний — аварійна зупинка!
        if (activeProfiles.length == 0) {
            throw new IllegalStateException(
                "ФАТАЛЬНА ПОМИЛКА: Додаток не може бути запущений без зазначення активного профілю! "
                + "Задайте змінну SPRING_PROFILES_ACTIVE (наприклад: dev, stage, prod)."
            );
        }
    }
}

🎯 Шпаргалка для інтерв’ю

30-секундна відповідь

Spring Profiles — інструмент сегментації конфігурації додатка під різні оточення (dev, stage, prod).

  • Дозволяє активувати профіль-специфічні файли налаштувань (application-{profile}.yml), властивості яких мають пріоритет над базовим application.yml.
  • Анотація @Profile над класами та @Bean-методами умовно реєструє біни в контексті. Починаючи зі Spring 5.1, підтримуються логічні вирази (prod & !cloud, dev | test).
  • У Spring Boot 2.4+ з’явилися Profile Groups (spring.profiles.group) для об’єднання наборів профілів та мультидокументні YAML-файли.
  • Профілі задаються під час старту JVM і не призначені для динамічного перемикання бізнес-фіч на льоту (для цього використовуються Feature Flags).

4 каверзних питання з відповідями

  1. Як працюють логічні оператори в анотації @Profile і з якої версії Spring вони доступні? Відповідь: Починаючи зі Spring Framework 5.1, у рядку @Profile можна використовувати оператори & (логічне І), | (логічне АБО), ! (логічне НЕ) та круглі дужки для групування умов (наприклад: @Profile("(dev | test) & !docker")). Якщо умова хибна, ProfileCondition відбраковує реєстрацію BeanDefinition.

  2. У чому різниця у пріоритеті між application.yml та application-prod.yml? Відповідь: При активному профілі prod спочатку завантажується базовий application.yml, а потім поверх нього накочується application-prod.yml. Якщо одна й та сама властивість оголошена в обох файлах, значення з application-prod.yml перевизначить дефолтне.

  3. Що таке Profile Groups у Spring Boot? Відповідь: Механізм групування, що з’явився у Spring Boot 2.4. Дозволяє через властивість spring.profiles.group.{groupName} оголосити складений псевдонім для набору профілів (наприклад: група production автоматично активує профілі prod, db-postgres, metrics-datadog).

  4. У чому принципова різниця між Spring Profiles та Feature Flags? Відповідь: Profiles визначають статичну інфраструктурну конфігурацію і вимагають обов’язкового перезапуску JVM для зміни оточення. Feature Flags (фіче-тогли) керують поведінкою бізнес-логіки і перемикаються динамічно в рантаймі без перезапуску сервісу (часто індивідуально для конкретних користувачів).

Червоні прапорці (чого категорично не можна говорити)

  • ❌ «Профілі можна динамічно перемикати в рантаймі без перезавантаження JVM» — профілі визначають склад бінів у контексті, який заморожується після виклику context.refresh().
  • ❌ «Feature Flags та Spring Profiles — це одне й те саме» — прапорці змінюють логіку на льоту, профілі конфігурують інфраструктуру на старті.
  • ❌ «Секретні паролі та токени продакшену безпечно комітити в application-prod.yml» — секрети повинні впроваджуватися через змінні оточення, HashiCorp Vault або Kubernetes Secrets.
  • ❌ «Базовий application.yml має пріоритет над application-prod.yml» — профільний файл завжди має пріоритет над базовим.

Пов’язані теми