УРОК 4 / 30 0%

Конфигурация: application.yml + профили

🎯

Цель урока

Сделать полноценный CRUD для задач с правильными HTTP-статусами (201/204/404), обработкой ошибок, и подготовить Task к подключению JPA.

🧠

Теория · для собеса

1 Откуда Spring Boot берёт настройки (приоритет, от высокого к низкому)
  1. Devtools global settings (~/.spring-boot-devtools.properties)
  2. @TestPropertySource` на тестах
  3. Command line args (--server.port=9090)
  4. Environment variables (SERVER_PORT=9090)
  5. application-{profile}.yml (например, application-prod.yml)
  6. application.yml (общий)
  7. Defaults (из кода библиотек)
2 Файл application.yml vs application.properties
  • .yml — читабельнее, иерархия
  • .properties — проще, плоский
  • Можно использовать оба одновременно (.yml загружается первым)
3 Профили

Профиль = набор конфигурации для конкретного окружения (dev, test, prod).

Активация:

  • application.yml: spring.profiles.active: dev
  • Env var: SPRING_PROFILES_ACTIVE=prod
  • JVM arg: -Dspring.profiles.active=prod
  • В @SpringBootTest: @ActiveProfiles("test")

При запуске с dev Spring читает:

  1. application.yml (все настройки)
  2. application-dev.yml (специфичные для dev, перезаписывают общие)

---

💻

Практика: улучшаем CRUD

  1. 1
    application.yml (общий)
    
    spring:
      application:
        name: taskflow
      profiles:
        active: dev   # профиль по умолчанию
    
    server:
      port: 8080
      servlet:
        context-path: /
    
    taskflow:
      app:
        version: 0.1.0
        pagination:
          default-page-size: 20
          max-page-size: 100
    
  2. 2
    application-dev.yml
    
    server:
      port: 8080
    
    logging:
      level:
        com.taskflow: DEBUG
        org.springframework.web: DEBUG
        org.hibernate.SQL: DEBUG
        org.hibernate.orm.jdbc.bind: TRACE
    
    spring:
      datasource:
        url: jdbc:postgresql://localhost:5432/taskflow_dev
        username: taskflow
        password: dev_password
        driver-class-name: org.postgresql.Driver
      jpa:
        hibernate:
          ddl-auto: validate
        show-sql: true
    
  3. 3
    application-prod.yml
    
    server:
      port: 80
      compression:
        enabled: true
        mime-types: application/json,text/html,text/plain
    
    logging:
      level:
        com.taskflow: INFO
        root: WARN
      file:
        name: /var/log/taskflow/app.log
    
    spring:
      datasource:
        url: ${DB_URL}  # из ENV-переменной
        username: ${DB_USERNAME}
        password: ${DB_PASSWORD}
        hikari:
          maximum-pool-size: 20
          minimum-idle: 5
      jpa:
        hibernate:
          ddl-auto: validate
        show-sql: false
        open-in-view: false
    
  4. 4
    application-test.yml
    
    spring:
      datasource:
        url: jdbc:tc:postgresql:16:///taskflow_test
      jpa:
        hibernate:
          ddl-auto: create-drop
    
  5. 5
    Типизированная конфигурация: @ConfigurationProperties

    Продолжаем тот же TaskFlow из урока 3 и добавляем новый файл конфигурации: config/AppProperties.java.

    
    package com.taskflow.config;
    
    import org.springframework.boot.context.properties.ConfigurationProperties;
    import org.springframework.context.annotation.Configuration;
    
    @Configuration
    @ConfigurationProperties(prefix = "taskflow.app")
    public class AppProperties {
        private String version = "0.0.0";
        private Pagination pagination = new Pagination();
    
        public String getVersion() { return version; }
        public void setVersion(String version) { this.version = version; }
        public Pagination getPagination() { return pagination; }
        public void setPagination(Pagination pagination) { this.pagination = pagination; }
    
        public static class Pagination {
            private int defaultPageSize = 20;
            private int maxPageSize = 100;
            public int getDefaultPageSize() { return defaultPageSize; }
            public void setDefaultPageSize(int defaultPageSize) { this.defaultPageSize = defaultPageSize; }
            public int getMaxPageSize() { return maxPageSize; }
            public void setMaxPageSize(int maxPageSize) { this.maxPageSize = maxPageSize; }
        }
    }
    

    Использование в уже существующем TaskService: не меняем репозиторий, а только нормализуем входной параметр.

    
    @Service
    public class TaskService {
        private final AppProperties props;
    
        public TaskService(AppProperties props) {
            this.props = props;
        }
    
        public int normalizePageSize(int requestedSize) {
            int max = props.getPagination().getMaxPageSize();
            int defaultSize = props.getPagination().getDefaultPageSize();
            if (requestedSize <= 0) {
                return defaultSize;
            }
            return Math.min(requestedSize, max);
        }
    }
    
  6. 6
    Показываем version в /api/health
    
    @RestController
    @RequestMapping("/api/health")
    public class HealthController {
        private final AppProperties props;
        public HealthController(AppProperties props) { this.props = props; }
    
        @GetMapping
        public Map health() {
            return Map.of(
                "status", "UP",
                "version", props.getVersion(),
                "timestamp", Instant.now().toString()
            );
        }
    }
    
  7. 7
    Запуск с разными профилями
    
    # dev (по умолчанию)
    ./gradlew bootRun
    
    # prod
    SPRING_PROFILES_ACTIVE=prod ./gradlew bootRun
    
    # С аргументами JVM
    ./gradlew bootRun --args='--spring.profiles.active=prod --server.port=80'
    
    # С переменными окружения (как в Docker/K8s)
    SPRING_PROFILES_ACTIVE=prod \
    DB_URL=jdbc:postgresql://prod-db:5432/taskflow \
    DB_USERNAME=app \
    DB_PASSWORD=secret \
    ./gradlew bootRun
    

    ---

🎯

Зачем это на собесе

После урока ты должен уметь ответить на:

12-Factor App: конфигурация через ENV-переменные, не вшита в код.
Docker/K8s: секреты (пароли, токены) передаются через env: в compose / Secret в k8s — нельзя хардкодить.
Профили позволяют держать разные настройки (dev локально, prod в облаке) в одном проекте.
@ConfigurationProperties — типизированная конфигурация, проверяется на этапе запуска (если поля нет в yaml — приложение упадёт сразу, а не в рантайме).

5 вопросов на углубление

Раскрой вопрос и нажми «🤔 Хочу разобрать подробнее» — он попадёт в страницу ответов.

1
@Value vs @ConfigurationProperties — когда что использовать?

Разбор внутри: одиночные настройки против типизированного объекта конфигурации.

2
Что будет с отсутствующими и лишними свойствами в @ConfigurationProperties?

Разбор внутри: дефолты, fail-fast и защита от опечаток в YAML.

3
Как Spring читает ENV-переменные вроде DB_PASSWORD?

Разбор внутри: placeholders, дефолты и relaxed binding ENV-переменных.

4
Что такое open-in-view: false и почему это важно?

Разбор внутри: граница транзакции, lazy loading и неожиданные SQL-запросы из JSON-сериализации.

5
Как работают profile groups в Spring Boot?

Разбор внутри: production/staging/local как удобные группы профилей.

Готов идти дальше?

Выбери вопросы, которые тебе интересны, и изучи их. Потом — к следующему уроку.

🚀 Перейти к Уроку 5

Сначала пройди все секции и выбери хотя бы 1 вопрос

⬅️ Назад к Уроку 3
🎉
Новый тир
Новый тир достигнут!