Как организовать разные URL для нескольких стендов при использовании RestTemplate в Spring
Что проверяют
Умеете ли вы выносить конфигурацию из кода так, чтобы один и тот же артефакт запускался на dev, test и prod без пересборки. Следом часто спрашивают, как подменить URL в интеграционных тестах и чем RestTemplate сегодня заменяют.
Как отвечать
Каркас из трёх шагов:
- URL в свойствах по профилям: общий
application.ymlплюсapplication-dev.ymlиapplication-prod.ymlсо своимclients.payment.base-url. Профиль выбирается при запуске черезSPRING_PROFILES_ACTIVE. Можно и не плодить файлы, а переопределять значение переменной окружения: благодаря relaxed bindingCLIENTS_PAYMENT_BASEURLперекроет свойство, и стенд в Kubernetes задаётся через ConfigMap; - типизированные настройки: вместо россыпи
@Valueзавести@ConfigurationProperties(prefix = "clients.payment")в виде record сbaseUrlи таймаутами, добавить@Validatedи@NotBlank, чтобы приложение не стартовало с пустым URL; - базовый URL внутри бина: создавать
RestTemplateчерезRestTemplateBuilderсrootUri(props.baseUrl()). Тогда вызовы в коде идут по относительному пути:restTemplate.getForObject("/orders/{id}", Order.class, id). На каждый внешний сервис свой бин со своим@Qualifier, таймаутами и авторизацией.
Стоит упомянуть, что RestTemplate находится в режиме поддержки, а для нового кода Spring предлагает RestClient (появился в Spring Framework 6.1): там то же самое делается через RestClient.builder().baseUrl(...). Подход с профилями и свойствами от этого не меняется.
Где спотыкаются
Склеивают URL строками в каждом сервисе, вроде baseUrl + "/data". Базовый адрес размазан по коду, а лишний или пропущенный слеш даёт 404 только на одном стенде.
Кладут адреса и секреты prod-стенда в репозиторий. Адрес ещё терпимо, а пароли и токены должны приезжать из переменных окружения или хранилища секретов.
Не знают, как подменить URL в тестах. Для кода на RestTemplate есть MockRestServiceServer, для интеграционных тестов WireMock или Testcontainers, чей адрес подставляется через @DynamicPropertySource.