Как реализовать вызов одного сервиса из другого
Что проверяют
Отличает ли кандидат вызов соседнего бина от сетевого вызова другого сервиса, и знает ли, что делает такой вызов пригодным для прода: таймауты, ретраи, деградация. Сам HTTP-клиент здесь наименее интересная часть.
Как отвечать
Первым делом уточнить постановку. Если оба сервиса это классы внутри одного приложения, ответ короткий: внедряется интерфейс, обсуждать надо циклические зависимости и границы транзакций. На собеседовании почти всегда имеют в виду сетевой вызов между процессами, дальше про него.
Чем звать:
RestClient(Spring 6.1+) для синхронных вызовов,WebClientдля реактивных и асинхронных;- декларативный HTTP interface с
@HttpExchangeили OpenFeign, если клиентов много и хочется описывать их интерфейсами; java.net.http.HttpClientс Java 11, если Spring нет;RestTemplateнаходится в режиме поддержки, новый код на нём не пишут.HttpURLConnectionдля прикладного кода не подходит: многословен и провоцирует забыть таймауты.
RestClient client = RestClient.builder()
.baseUrl("https://billing.internal")
.requestFactory(factoryWithTimeouts()) // connect и read обязательно
.build();
InvoiceDto invoice = client.get()
.uri("/invoices/{id}", id)
.retrieve()
.onStatus(HttpStatusCode::is4xxClientError, (req, res) -> { throw new InvoiceNotFound(id); })
.body(InvoiceDto.class);Что превращает этот код в боевой:
- Таймауты на подключение и на чтение, и они должны быть меньше таймаута того, кто вызывает вас. Клиент без таймаута рано или поздно съест весь пул потоков.
- Ретраи только для идемпотентных операций, с экспоненциальной задержкой и джиттером. Ретрай
POSTна создание без ключа идемпотентности порождает дубли, а синхронные повторы без джиттера складываются в шторм. - Circuit breaker и bulkhead (Resilience4j): падение соседа не должно ронять вас следом. Плюс продуманный фолбэк: кеш, частичный ответ, понятная ошибка.
- DTO вместо разбора строк, аккуратная обработка не-2xx: тело ошибки обычно несёт доменный код, который нужен вызывающему.
- Проброс контекста: заголовки трассировки и авторизация. Между сервисами это обычно
client_credentialsили mTLS, а не пользовательский токен. - Метрики и логи с длительностью и кодом ответа, иначе разбор инцидента упирается в догадки.
Если ответ не нужен немедленно, лучший вариант вообще не звать синхронно: событие в Kafka или RabbitMQ снимает связность и каскадные отказы.
Где спотыкаются
Пишут вызов без таймаута и без разбора не-2xx, считая любой ответ успехом. Второе: включают ретраи глобально на все методы, включая создание сущностей, и получают дубликаты в базе.