Axelix. Cпецназ для Вашей Spring Boot экосистемы

Страницы:  1

Ответить
 

Professor Seleznov


Привет, Хабр!
Меня зовут Михаил Поливаха. Я думаю, что в Хабе Spring АйО меня уже относительно знают. В рамках Spring АйО мы довольно часто занимаемся подбором технического материала и его ревью. Сам же я довольно регулярно выступаю на конференциях, контрибьючу в Open Source и т.д.
Также, часто наши материалы крутятся вокруг Java разработки и конечно же Spring-а. И данной пост, хоть и будет с одной стороны сильно связан с Java и Spring Framework, но, тем не менее, не похож на остальные.
На днях произошло довольно знаковое событие. Мы с небольшой командой примерно год писали инструмент, который призван существенно упростить весь процесс отладки, тестирования и мониторинга Spring Boot приложений в production. И вот этот проект наконец-то получил первый Milestone Релиз.
Проект называется Axelix.
Думаю, что важно сразу отметить: ядро проекта Axelix является Open Source продуктом и доступно на GitHub. Проект будет следовать модели Open Core. Поэтому Вы можете спокойно ставить его себе в контур так же, как ставите, например, Grafana.
В данном посте я хочу рассказать про историю проекта, то, зачем он нужен, и как мы собтсвенно вообще к нему пришли. В нём будет анализ причин появления Axelix и глубокий обзор конкретного кейса решения повседневных проблем в рамках разработки на Spring Boot.
Надеюсь, что Вам будет интересно. Ну что же, начнём
Сотояние Индустрии Enterprise Софта
Типичный enterprise имеет много внутреннего софта. Этот софт решает разные задачи и обычно написан на нескольких языках программирования — Java, Go, C# и т.д. Один из самых популярных ЯП для server-side/backend-приложений в крупных компаниях — это Java. На это есть много исторических причин, но факт остаётся фактом.
Кроме того, самый популярный фреймворк для написания backend-приложений на Java — бесспорно Spring Boot. Очень упрощённо: это дополнительный слой поверх Spring Framework, который упрощает его конфигурацию (хотя, сейчас Spring Boot уже гораздо больше, чем просто автоконфигурация). По ряду причин это безоговорочно самый популярный выбор в enterprise. Например, Netflix мигрировал свою экосистему (более 3000 приложений) на Spring Boot 3 и планирует оставаться на Spring Boot.
Итого: Spring Boot победил. Это очень популярный выбор на backend при разработке на Java. Это то, что у нас есть сегодня и с этим странно спорить.
Мотивация и Pain Point
Что интересно: Spring Framework изначально начинался как IoC-контейнер с dependency injection в качестве главной фичи, а сейчас это совсем другое — намного, намного больше. Сегодня Spring Framework / Spring Boot — целая экосистема, которая поддерживает весь технологический стек нашего приложения:
  • Обработка HTTP-запросов
  • Работа с RDBMS — от управления транзакциями до ORM
  • Отправка сообщений в message broker-ы и многое другое
В результате экосистема Spring Framework существенно выросла в сложности. В современных Spring Boot microservice-ах у нас очень много движущихся частей. Например:
  • В приложениях много bean-ов. Часть приходит из Spring Boot и auto-configuration, часть — из внутренних платформенных библиотек, которые мы используем в компании, а часть мы создаём сами.
  • У нас довольно много properties, которые могут приходить из разных источников: из файла application.yaml, из Spring Cloud Config, из environment переменных внутри Pod-ов в K8S, из аргументов командной строки и так далее.
  • Конечно же, у нас много proxy! И все они работают друг с другом: transactional proxy, proxy для OpenFeign client-ов, proxy для Kafka Listener-ов и т.д.
Идея в том, что Spring Framework стал довольно большим и породил много взаимосвязанных частей, где каждая влияет на другую (например, properties влияют на создание bean-ов, созданные bean-ы могут подтянуть ещё properties и т.д.). Есть даже такое мнение, что из-за всего этого Spring Framework превратился в большого неукротимого монстра.
Подумайте сами: почти на любой Java-конференции — и внутри России, и даже за рубежом (говорю как спикер на международных конференциях, в том числе) — добрая половина докладов посвящена Spring Framework, тому как он устроен, или похожим темам.
Почему? Потому что Spring а) везде, и б) он сложен!
И, следуя из этого, за годы написания Spring Boot-приложений я с почти 100%-ной уверенностью могу сказать: когда мы, Java-разработчики на Spring Boot, деплоим приложения в любое окружение, мы снова и снова сталкиваемся с одними и теми же проблемами. Например:
  • Мы делаем HTTP-запросы внутри транзакций и исчерпываем пул db-connection-ов.
  • Мы создаем N + 1 и сталкиваемся с проблемами lazy loading в Spring Data JPA.
  • Мы дебажим ситуации, когда в @Value / @ConfigurationProperties подтянулось не то property.
  • Мы дебажим случаи, когда наш @Bean был “подавлен” из-за какого-то bean-а из @AutoConfiguration или платформенной либы и так далее…
Все эти проблемы реальны. Если Вы писали Spring Boot-приложения хотя бы пару лет, уверен, хотя бы часть из них Вам знакома.
А теперь: что, если я скажу, что боль от разработки Spring Boot backend-ов можно облегчить?
Вдумайтесь: сегодня по всему миру сотни и сотни команд пишут Spring Boot backend-ы и снова и снова борются с одними и теми же проблемами.
Можно ли это как-то решить? Давайте попробуем разобраться на примере.
Типичный День Spring Boot-разработчика
Чтобы глубже понять, что именно Axelix пытается решить, пройдёмся по паре примеров Java-кода. Представьте, что в приложении есть следующий код ниже. У нас есть Spring бин, который мы используем, чтобы получить набор permissions, которыми обладает гипотетический клиент:
@Slf4j
@Service
public class PermissionsResolver {
@Value("${permission.caching.enabled:true}")
private boolean cacheLookupsEnabled;
@Autowired
private ExternalPermissionsResolver delegate;
@Autowired
private PermissionsCache cache;
@Autowired
private DetailsResolver detailsResolver;
@PostConstruct
void init() {
log.debug("Permissions cache lookup enabled: {}", cacheLookupsEnabled);
}
@Transactional
public Set<PermissionDto> resolveClientPermissions(Client client) {
try {
Set<PermissionDto> permissions = delegate.resolve(client);
for (Permission permission : permissions) {
PermissionDetails details = detailsResolver.resolvePermissionDetails(permission);
permission.setDetails(details);
}
return permissions;
} catch (SomeExpectedException e) {
if (cacheLookupsEnabled && client.getStatus() == ClientStatus.EXISTING) {
log.debug("For the client '{}' taking permissions from the cache", client.getId());
return cache.get(client.getId());
} else {
throw e;
}
}
}
}
Это классический Java-код на стеке Spring Boot. Давайте честно: мы же все такое видели. Все такой код отлаживали, все много-много раз дорабатывали такой код.
И во время регрессионного тестирования QA-инженер приходит и говорит:
Привет! На dev/qa/sandbox/pre-prod окружении я не могу создать XYZ — пишет, что у моего client-а нет нужных permissions
Вы открываете IDE (или AI Agent, что уж там 😈) и пытаетесь разобраться в кодовой базе. Ищете, что может пойти не так.
Часть 1. Смена Уровня Логирования
А вариантов того, что может пойти не так в Spring Boot приложении — просто вагон.
Например, может оказаться, что мы берём client-ов из кэша. Ну, по крайней мере, кэш должен быть включён, верно? Или нет… И, как всегда, нужный нам log — в DEBUG, а не в INFO. И, конечно, logback — дефолтная система логирования под Spring Boot — его отфильтровывает и не пропускает.
Разве не было бы круто временно поднять уровень логирования для этого класса до DEBUG? Без редеплоя, буквально на 10 минут? Axelix позволяет временно изменить уровень логирования — только для отладки, — а потом откатить его назад. Кстати, очень скоро Axelix сможет откатить уровень логгирования сам, без необходимости разработчику вручную возвращать всё как было.
Часть 2. Загадочное Значение Property
Вы копаете дальше и понимаете: поведение некорректно, потому что на pre-prod мы вообще не должны брать client-ов из кэша. Мы должны упасть и вернуть соответствующий status code.
Теперь вопрос: почему, чёрт возьми, кэш вообще включён? Мы же явно отключили его через переменную окружения… Может, где-то есть другой property (например, из Spring Cloud Config), который переопределяет наш? А что, если Spring вообще не увидел нашу переменную окружения, отключающую кэш?
Это открытые вопросы, и поверьте, Вы не один такой. С этим сталкивается очень много людей.
Axelix может не только показать, какие properties знает Spring, но и сказать, какое property в итоге победило и подавило остальные — и откуда оно пришло.
Часть 3. Зоопарк Bean-ов
Ладно, Вы выяснили, что property на самом деле пришло из VCS через Spring Cloud Config, и какой-то инженер просто его поменял и забыл сказать (упс, как неловко-то вышло). Вы, скаля зубы, выключили кэширование и попросили QA перезапустить сценарий. Client, очевидно, по-прежнему без permissions, но хотя бы теперь мы отдаём ошибку, а не ответ из fallback-кэша.
Теперь нужно понять, почему permissions нет. QA мамой клянётся, что добавил их в базу данных или попросил кого-то это сделать. Вы ему доверяете, но (не на все 100%, конечно) и снова открываете IDE. И вот что находите:
public interface ExternalPermissionsResolver {
Set<PermissionDto> resolve(Client client);
}
@Component
@ConditionalOnProperty(prefix = "permissions.resolver", value = "source", havingValue = "legacy", matchIfMissing = true)
@ConditionalOnMissingBean(ExternalPermissionsResolver.class)
public class LegacySystemPermissionsResolver implements ExternalPermissionsResolver {
@Override
public Set<PermissionDto> get(Client client) {
// implementation
}
}
@Component
@ConditionalOnProperty(prefix = "permissions.resolver", value = "source", havingValue = "database")
@ConditionalOnDatabaseAvailable
@ConditionalOnMissingBean(ExternalPermissionsResolver.class)
public class DatabaseSystemPermissionsResolver implements ExternalPermissionsResolver {
@Override
public Set<PermissionDto> get(Client client) {
// implementation
}
}
Итак, на pre-prod мы не хотим использовать старый LegacySystemPermissionsResolver — мы от него избавляемся. Есть ли шанс, что он всё ещё используется? По крайней мере, мы можем посмотреть properties и их фактические значения — Axelix уже помогает с этим. Однако это ещё не отвечает на главный вопрос: а мы на 100% уверены, что используем DatabaseSystemPermissionsResolver? А что, если нет?
Здесь Axelix может не только сказать, какие bean-ы реально активны, но и объяснить, откуда они взялись. Более того, Axelix может объяснить, почему bean-ы, которые мы ожидали увидеть созданными, на самом деле не были созданы.
Например, наш DatabaseSystemPermissionsResolver мог не создаться, потому что кастомная conditional-аннотация @ConditionalOnDatabaseAvailable не прошла, — и в итоге создался fallback LegacySystemPermissionsResolver.
Часть 4. Hibernate всегда рядом, чтобы всё испортить
Уже измотанный, измучанный, бедняга, Вы решили и эту проблему. Причина сломанных permissions — использование не того ExternalPermissionsResolver. Тестирование на pre-prod завершено, наконец. Столько времени ушло на отладку — это просто безумие!. Но разработчики для этого и существуют, чтобы страдать. Вы это принимаете и возвращаетесь к работе.
Только собрались с мыслями, сели за тикет — прилетает отчёт от нагрузочного тестирования:
95-й перцентиль latency улетел в космос: 5–6 секунд! Такое нельзя в прод катить!
Приложение под нагрузкой почему-то начинает тормозить. Вы не знаете, в чём дело. Но раз мы меняли только логику разрешения permissions, проблема, должно быть, где-то там.
Вы снова внимательно смотрите на код и понимаете:
Чёрт! Снова N + 1! DatabaseSystemPermissionsResolver возвращает entity без JOIN FETCH-нутой коллекции permissions!
Дъявол! А ведь проблема с N + 1 коварна тем, что её не так просто поймать в тестах! Конечно, Hibernate даёт статистику для её детекта, но никто же не заморочился писать тесты с глубоким анализом Hibernate statistics!
И этот код успешно прошёл:
  • Code Review
  • Quality Gate
  • Функциональное тестирование от QA
  • Security проверки от Sonar Qube и т.д.
Все барьеры пройдены! А теперь… Теперь нужно менять код, мержить, перетестировать, заново гонять security scan. Это такая боль! В агонии начинаете чувствовать, что ненавидите работу, а Hibernate — ещё больше, потому что всё так неочевидно!
Но не переживайте — Axelix может помочь. Axelix может обнаружить N + 1 запросы на testing/qa/sandbox окружениях задолго до деплоя на pre-prod или стенд нагрузочного тестирования.
Вот и всё. Более того, Вы не только можете обнаружить N + 1 запросы, но и точно сказать, какие запросы Hibernate выполнил под капотом в рамках этой @Transactional и сколько она длилась.
Наконец, если не хотите использовать UI Axelix, в ближайшем будущем можно будет настроить экспорт этих данных в любое APM-решение на Ваш вкус — от простого экспорта дополнительных Prometheus Metrics, до современного Open Telemetry — и смотреть те же данные из Axelix в привычной Grafana, например.
Интеграция с AI. MCP Server.
Фичи выше — это примерно ~30% того, что Axelix умеет сейчас; там гораздо больше. Я не перечисляю всё, иначе статья станет очень длинной. Но есть ещё один слон в посудной лавке, которого нельзя игнорировать — AI.
Сегодня мы все пользуемся AI Agent-ами: Cursor, Codex, Claude и т.д. Давайте честно: они занимают значительную часть нашей жизни как Software Engineer-ов. В Axelix мы это прекрасно понимаем; у нас AI:
  • Помогает ревьюить код в monorepo
  • Помогает писать интернационализированную документацию и т.д.
Мы признаём влияние и ценность AI. Поэтому во всех дистрибутивах Axelix идёт встроенный MCP server, который отдаёт все описанные выше (и не только) возможности AI Agent-у через embedded MCP server. Хотите отлаживать проблемы через AI Agent — пожалуйста.
Правило, которому мы следуем: всё, что доступно человеку через UI, также доступно AI Agent-ам через MCP server.
Сравнение со Spring Boot Admin
Уверен, опытные разработчики слышали про Spring Boot Admin. Этот инструмент работает как proxy к существующим Spring Boot Actuator endpoint-ам и даёт часть возможностей, которые умеет Axelix.
Мы настолько часто объясняли, чем Axelix отличается от Spring Boot Admin, что даже сделали отдельную страницу в документации, где объясняем, какие проблемы со Spring Boot Admin мы видим и какую роль Axelix играет в общей картине. Прочитайте, если интересно.
Заключение. Что дальше
Повторю: мы только что выпустили первый milestone. GA-релиза пока нет, но мы планируем его во второй половине лета 2026 года. Чтобы быть в курсе, мы завели telegram-канал для новостей о фичах и релизах. Подписывайтесь, если хотите следить за проектом.
У нас уже есть pilot-команды, которые поставили Axelix в свои окружения — как Helm Chart, Docker image или даже просто JAR. Мы по-прежнему активно обрабатываем feedback от пилотных команд. Если хотите поставить Axelix — Вы уже можете это сделать, документация доступна на русском и английском. В этом случае Вы сможете дать feedback и существенно повлиять на развитие Axelix.
Мы активно полируем документацию; если понадобится помощь — пишите напрямую администраторам Telegram-канала проекта Axelix. Поможем с установкой.
Надеюсь, статья была интересной. Если любопытно, как это устроено — исходники Axelix Core на GitHub. Изучайте на здоровье!
P.S: Мы приветствуем контрибьюторов! Хотите помочь — загляните в contribution guidelines нашего проекта. Будем рады увидеть PR/Issue от Вас когда-нибудь!-Источник
 
Loading...
Error