1. Boot «зробив щось сам»
Базовий Actuator уже дав нам health, info і звичку акуратно поводитися з політикою exposure. Тепер нам потрібен наступний щабель діагностики: зрозуміти, чому Boot увімкнув саме такі гілки конфігурації, що реально піднялося у web-шарі, як застосунок дійшов до READY, що відбувається під час запитів і як окремо перевірити, що сервіс не просто живий, а ще й корисний. Почнемо з найнеприємнішого запитання: чому застосунок узагалі зібрано саме так.
Якщо ви колись ловили себе на думці «я просто додав залежність… і все раптово запрацювало / зламалося», то ви вже познайомилися з головним психологічним ефектом Spring Boot: відчуттям, ніби за вашою спиною хтось бігає та переставляє тумблери. Це відчуття небезпечне не тому, що Boot поганий, а тому що в здогадках неможливо налагоджувати систему: ви або починаєте метушитися, або копіюєте чужі рішення, не розуміючи, чому вони взагалі допомагають.
Доросле інженерне запитання звучить так: «Чому застосунок зібрано саме так?» Не «що він робить», не «як виправити симптом», а саме «чому так сталося». І це запитання буває дуже практичним. Наприклад, чому в нас піднявся вбудований сервер, чому JSON віддається без наших налаштувань, чому якийсь бін зʼявився «сам», а інший не зʼявився, хоча ми впевнені, що має.
Endpoint conditions — це спосіб отримати відповідь у стилі «тому що…» з переліком причин. Він перетворює «магічну поведінку» на доведену поведінку. І це, чесно кажучи, краще за будь-який шаманський танець навколо application.yaml.
2. Що показує endpoint conditions
Ззовні все виглядає просто: ви заходите на /actuator/conditions і отримуєте величезний JSON. Усередині сенс дуже конкретний: це представлення під час виконання того, що Spring Boot вирішив увімкнути або вимкнути на етапі auto-configuration, і чому він так вирішив. Тобто ми не вгадуємо «мабуть, через профіль», а читаємо: «увімкнено, тому що такий-то клас знайдено на classpath», або «не увімкнено, тому що property має значення X», або «не увімкнено, тому що вже існує користувацький бін».
У conditions є важлива особливість, яку потрібно прийняти заздалегідь: negative matches — це не помилка. У типовому застосунку негативних збігів буде багато, тому що Boot містить гілки конфігурації на всі випадки життя: різні web-рушії, різні формати, різні середовища. Ваш catalog-service — невеликий сервіс. Йому не потрібно вмикати «все». Тому величезний список «не збіглося» — це нормальна картина, а не привід для паніки.
Щоб було легше тримати в голові, можна мислити так: conditions — це «протокол рішення» щодо інфраструктури. Він каже не тільки «винен / невинний» (увімкнено / вимкнено), а й наводить аргументацію. Якщо ви колись читали судові рішення або хоча б користувацьку угоду, то ідея схожа: тексту багато, але читати треба цілеспрямовано.
Невелика таблиця, яка допомагає зорієнтуватися в тому, що ви побачите:
| Частина відповіді conditions | Як це зрозуміти по-людськи | Коли дивитися |
|---|---|---|
| positiveMatches | «Ця конфігурація увімкнулася, і ось причини» | Коли ви питаєте «чому воно взагалі зʼявилося?» |
| negativeMatches | «Ця конфігурація не увімкнулася, і ось причини» | Коли ви чекаєте функціональність, але її немає |
| unconditionalClasses (або аналогічний блок) | «Це вмикається завжди, без умов» | Коли здається, що «це точно auto-config?», а воно базове |
Назви полів можуть трохи відрізнятися залежно від версії, але сенс у всіх реалізацій один: показати набір джерел, зазвичай auto-configuration класи, і результати перевірки умов.
3. Як увімкнути conditions у catalog-service
Endpoint conditions — дуже корисний, але він належить до діагностичних. Він показує внутрішню картину збирання застосунку, а це не те, чим потрібно тішити випадкову людину зовні. Тому ми робимо рівно те, що вже закріпили для Actuator: вмикаємо й експонуємо такі речі лише в локальному профілі або в dev, якщо у вас є окреме dev-середовище.
Найпростіший і найзрозуміліший варіант — додати conditions до вже наявного списку експонованих endpoints у application-local.yaml. Якщо у вас там уже є env або configprops, не замінюйте їх цією строкою: сенс у тому, щоб додати conditions, а не переписати весь include з нуля.
# src/main/resources/application-local.yaml
management:
endpoints:
web:
exposure:
# Зберігаємо вже відкриті local/dev endpoints і додаємо conditions
include: "health,info,env,configprops,conditions"
Тепер, якщо ви запускаєте застосунок із профілем local, у вас зʼявиться доступ до:
GET http://localhost:8080/actuator/health
GET http://localhost:8080/actuator/info
GET http://localhost:8080/actuator/conditions
Якщо у вас у local уже залишені env і configprops, вони продовжать працювати в цьому ж профілі.
Якщо профілі у вас уже налаштовані і ви звикли запускати застосунок так:
# Запуск застосунку з активним профілем local
# (важливо: саме профіль визначає, які endpoints будуть експоновані)
./gradlew bootRun --args='--spring.profiles.active=local'
то все має бути передбачувано: endpoint зʼявиться в local і зникне в prod, якщо там ви його не експонували.
Іноді новачок потрапляє в пастку: «я додав до include, але endpoint усе одно недоступний». У такому разі важливо памʼятати дві речі. По-перше, endpoint може бути не на classpath, якщо ви не підключили spring-boot-starter-actuator (у нас він підключений). По-друге, endpoint може бути увімкнений, але не експонований, або навпаки — залежно від політики. На нашому курсі ми тримаємо модель простою: у local експонуємо явно.
Для перевірки можна зробити просту річ: відкрити GET /actuator — там зазвичай видно, які endpoints узагалі доступні в поточному профілі. І вже потім переходити до /actuator/conditions.
4. Читаємо /actuator/conditions
Перше враження від /actuator/conditions зазвичай таке: «це що, весь інтернет у JSON?» І це нормальна реакція. Дані справді обʼємні, бо Spring Boot приймає багато рішень. Але читати їх «згори вниз» — це як намагатися навчитися готувати, зʼївши цілком кулінарну книжку. Непродуктивно і трохи небезпечно для психіки.
Правильна стратегія дуже проста: ви приходите в conditions з конкретним запитанням і шукаєте конкретний блок.
Можна тримати в голові такий алгоритм:
flowchart TD %% Алгоритм читання /actuator/conditions: рухаємося від запитання до конкретної конфігурації Q["Є запитання: «чому X?»"] --> F["Знаходимо пов'язану auto-configuration за назвою"] F --> P["Дивимося positive matches: чому увімкнулося"] F --> N["Дивимося negative matches: чому не увімкнулося"] P --> C["Звіряємо з профілем / properties / classpath"] N --> C C --> R["Робимо висновок: яку причину треба змінити"]
Ось приклади запитань, з якими conditions працює особливо добре:
Ви бачите, що застосунок віддає JSON, і хочете зрозуміти, завдяки якому механізму це «піднялося само». Ви додали Actuator і хочете переконатися, що саме він активний і які гілки конфігурації увімкнулися. Ви чекаєте якусь поведінку, але її немає — і хочете побачити, з якої причини конфігурація не спрацювала: немає класу, не те property чи конфлікт із вашим біном.
Технічно читати великий JSON найзручніше або в браузері, де є пошук по сторінці, або в IDE, якщо зберегти відповідь у файл. Для перших кроків достатньо банального Ctrl+F за назвою цікавої auto-configuration. Так, це не «суперінженерно», але зате чесно й працює у людей без ступеня з термінальних команд.
5. Мінікейси для catalog-service
Зараз ми зробимо саме те, заради чого endpoint conditions існує: візьмемо кілька ситуацій із життя catalog-service і подивимося, як перетворювати «мені здається» на «я бачу, чому». Ці кейси не потребують змінювати код просто зараз — вони потребують навчитися пояснювати те, що вже є, і це найважливіша навичка для роботи зі Spring Boot.
Web runtime без ручного налаштування
У catalog-service ми використовуємо spring-boot-starter-webmvc. Це означає, що на classpath потрапляє Spring MVC, вбудований servlet-контейнер і набір стандартних бінів, щоб запити почали доходити до ваших @RestController. Але новачкові легко переплутати: він бачить лише контролер, а звідки взявся сервер — «незрозуміло, мабуть, Boot».
І ось тут conditions дає спокійну відповідь: є auto-configuration, що відповідає за MVC-шар, і вона увімкнулася, тому що на classpath є потрібні класи та тому що ви не оголосили жодних конфліктних налаштувань.
У реальному виводі ви побачите багато деталей, але концептуально це виглядає приблизно так, спрощений фрагмент, щоб не втонути:
{
"positiveMatches": {
"WebMvcAutoConfiguration": [
// На classpath є ключові MVC-класи → можна вмикати MVC-шар
"OnClassCondition: found DispatcherServlet and WebMvcConfigurer",
// Користувач не перевизначив MVC повністю власною конфігурацією
"OnMissingBeanCondition: no user-defined WebMvcConfigurationSupport"
]
}
}
Сенс цього шматка не в тому, щоб запамʼятати точні назви умов, а в тому, щоб побачити механіку: Boot перевіряє «чи є класи, які означають MVC?», перевіряє «чи не взяли ви повний контроль над MVC вручну?», і якщо все гаразд — вмикає свою конфігурацію.
І ось тут зʼявляється дуже практичне застосування: якщо завтра ви випадково додасте щось, що змінює web-стек, наприклад інший starter, і застосунок почне поводитися інакше, conditions покаже, яка гілка стала активною і чому. Не «тому що Boot так захотів», а «тому що на classpath зʼявився клас X» або «тому що зʼявився бін Y».
Дуже важливий момент для спокою: поруч із цим ви майже напевно побачите негативні збіги для альтернативних реалізацій. Наприклад, конфігурації для іншого контейнера або іншого web-стеку. І це нормально: Boot знає про них, але не вмикає, бо у вас немає відповідних бібліотек.
Actuator: звідки беруться health/info
Коли ви підключили spring-boot-starter-actuator, ви додали до застосунку цілий діагностичний шар. І він теж вмикається не «за велінням небес», а через auto-configuration, яка бачить: на classpath є потрібні класи Actuator, отже можна реєструвати endpoints, health contributors та інші корисні речі.
У conditions ви зазвичай знайдете конфігурації, повʼязані з endpointʼами. У спрощеному вигляді логіка виглядає так:
{
"positiveMatches": {
"HealthEndpointAutoConfiguration": [
// Клас endpoint-а присутній → можна реєструвати health endpoint
"OnClassCondition: HealthEndpoint is present",
// Налаштуваннями нічого не заборонено → endpoint увімкнено
"OnPropertyCondition: endpoint enabled"
],
"InfoEndpointAutoConfiguration": [
// Те саме, але для info endpoint
"OnClassCondition: InfoEndpoint is present",
"OnPropertyCondition: endpoint enabled"
]
}
}
У реальному сервісі ви можете зіткнутися із ситуацією: «чому endpoint є, але він 404?» І тут важливо не плутати дві різні речі. Endpoint може бути зареєстрований — це про умови вмикання auto-config, — але не експонований назовні — це вже про management.endpoints.web.exposure.*. Ми вже обговорили політику exposure; conditions допомагає зрозуміти першу частину (endpoint створено?), а вже потім ви перевіряєте, чи відкрили його назовні.
Знову невелике психологічне розвантаження: негативні збіги тут теж будуть. Наприклад, конфігурації для метрик, трасування та інших речей, що потребують додаткових залежностей. Вони можуть бути в negativeMatches не тому, що «зламано», а тому, що «у нашому курсі й у нашому проєкті це не підключено». І це, насправді, дуже хороша новина: ви бачите межі поточного стеку за фактами.
Бін лише в local-профілі
Тепер зробимо приклад не про автоконфіги Boot, а про нашу умовну реєстрацію. Так, conditions — про auto-configuration, але він дуже добре пояснює і ті речі, які ви робите через умови (@Profile, @ConditionalOnProperty тощо). Це особливо корисно, коли ви починаєте будувати «local-only» діагностику і хочете бути певні, що вона не витече в prod.
Припустімо, ми додали маленький marker-bean, який існує лише в профілі local. Він нічого не робить, але чудово демонструє умовну реєстрацію.
package com.example.catalogservice.config;
/**
* Marker-клас для демонстрації умовної реєстрації біна.
* Жодної логіки не містить: його завдання — просто бути присутнім у контексті.
*/
class LocalDiagnosticsMarker {
// Порожній marker: його сенс — просто "існувати" в local-профілі
}
І конфігурація:
package com.example.catalogservice.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
@Configuration // Говоримо Spring, що це конфігураційний клас (джерело @Bean-методів)
class LocalDiagnosticsConfiguration {
@Bean // Реєструємо бін у контейнері Spring
@Profile("local") // Бін з'явиться лише якщо активний профіль local
LocalDiagnosticsMarker localDiagnosticsMarker() {
// Повертаємо marker-об'єкт: він потрібен, щоб було видно, з'явився бін чи ні
return new LocalDiagnosticsMarker();
}
}
Якщо ви запускаєте застосунок із --spring.profiles.active=local, цей бін зʼявиться в контексті. Якщо запускаєте без профілю — зникне.
Де тут conditions? Він корисний тим, що дозволяє побачити: бін або конфігурація не зʼявилися не «тому що Spring не побачив файл», а тому що умова профілю не збіглася. У виводі це виглядатиме як «збіглося / не збіглося» за умовою ProfileCondition. І саме це повертає нас до інженерної картини: результат визначається правилами, а не настроєм фреймворку.
До речі, цей приклад у реальному проєкті можна легко повʼязати з діагностикою Actuator: ви можете тримати розширену експозицію endpoints лише в local, а в prod залишити мінімум. І conditions буде одним із тих endpoints, які ви тримаєте в local виключно як інструмент розробника.
6. Типові помилки під час роботи з conditions
Помилка №1: читати вивід цілком, зверху вниз, як роман.
Вивід conditions великий, бо рішень у Boot багато. Якщо намагатися «прочитати все», мозок швидко переходить у режим «нічого не розумію». Набагато продуктивніше прийти із запитанням, знайти конкретну auto-configuration за назвою і вивчити лише її гілку: чому увімкнулася або чому не увімкнулася.
Помилка №2: сприймати будь-який negativeMatches як поломку.
У застосунку завжди буде безліч гілок, які не мають спрацьовувати. Наприклад, конфігурації для технологій, яких у вас немає в залежностях. Негативний збіг — це не діагноз, а пояснення: «ця гілка не підходить під поточний classpath / properties / біни». Помилкою це стає лише тоді, коли ви очікували увімкнення цієї гілки.
Помилка №3: забувати, що “увімкнено” і “експоновано” — різні речі.
Дуже часта ситуація з Actuator: endpoint у застосунку є (автоконфігурація його зареєструвала), але зовні він недоступний, тому що ви не ввімкнули його в management.endpoints.web.exposure.include. conditions допомагає зрозуміти першу частину (endpoint створено?), а exposure-налаштування — другу (endpoint відкрито назовні?).
Помилка №4: ігнорувати профілі та властивості під час інтерпретації результату.
Conditions майже завжди завʼязані на три речі: classpath, активні profiles і properties. Якщо ви дивитеся conditions, але не звіряєте його з тим, які профілі активні й які значення властивостей реально спрацювали, ви легко зробите хибний висновок. У нашому курсі для цієї звірки вже є знайомі інструменти: логи старту, env і configprops.
Помилка №5: намагатися знайти в conditions пояснення бізнес-помилок.
Endpoint conditions відповідає на запитання «чому інфраструктура зібралася так», а не «чому ваш сервіс повернув неправильний список курсів». Якщо у вас помилка у фільтрації, в in-memory репозиторії або в контролері — conditions не має її «пояснити». Він допоможе лише переконатися, що потрібні інфраструктурні частини взагалі увімкнені, і далі ви йдете в код і логи.
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ