REST-клиент: запросы к другим сервисам
Сервисы редко живут в одиночку: блогу может понадобиться профиль автора из сервиса пользователей, курс валют из банка, адрес из сервиса карт. Писать HTTP-запросы вручную долго. REST-клиент Quarkus работает наоборот по сравнению с ресурсом: вы описываете чужой API Java-интерфейсом с теми же аннотациями @Path и @GET, а Quarkus сам создаёт реализацию, которая отправляет запросы и превращает JSON в объекты.
Подключение
./mvnw quarkus:add-extension -Dextensions="rest-client-jackson"В качестве «сервиса пользователей» возьмём учебный публичный API jsonplaceholder.typicode.com. Его адрес /users/1 отдаёт JSON с полями id, name, email и многими другими.
Интерфейс клиента
// src/main/java/com/example/blog/User.java
package com.example.blog;
public record User(long id, String name, String email) {
}// src/main/java/com/example/blog/UserClient.java
package com.example.blog;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.PathParam;
import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;
@Path("/users")
@RegisterRestClient(configKey = "users-api")
public interface UserClient {
@GET
@Path("/{id}")
User getById(@PathParam("id") long id);
}@RegisterRestClient говорит Quarkus создать реализацию интерфейса. configKey — имя, по которому клиент ищет свои настройки. Лишние поля ответа Jackson пропускает, в запись попадут только id, name и email.
Адрес сервиса в настройках
# src/main/resources/application.properties
quarkus.rest-client.users-api.url=https://jsonplaceholder.typicode.com
quarkus.rest-client.users-api.connect-timeout=2000
quarkus.rest-client.users-api.read-timeout=5000Адрес не зашит в код, поэтому на сервере его можно поменять переменной окружения. Таймауты в миллисекундах не дадут запросу висеть бесконечно, если сервис не отвечает.
Использование клиента
Клиент внедряется с дополнительным квалификатором @RestClient:
// src/main/java/com/example/blog/AuthorResource.java
package com.example.blog;
import jakarta.inject.Inject;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.PathParam;
import org.eclipse.microprofile.rest.client.inject.RestClient;
@Path("/authors")
public class AuthorResource {
@Inject
@RestClient
UserClient userClient;
@GET
@Path("/{id}")
public User get(@PathParam("id") long id) {
return userClient.getById(id);
}
}curl http://localhost:8080/authors/1{"id":1,"name":"Leanne Graham","email":"Sincere@april.biz"}
Наш сервис сходил в чужой API и вернул сокращённый профиль автора.
Ошибки удалённого сервиса
Если удалённый сервис ответил 404, клиент бросает ClientWebApplicationException. Чтобы превратить такой ответ в своё исключение, в интерфейс добавляют статический метод с @ClientExceptionMapper:
// src/main/java/com/example/blog/UserClient.java
@ClientExceptionMapper
static RuntimeException toException(Response response) {
if (response.getStatus() == 404) {
return new AuthorNotFoundException();
}
return null;
}Импорты — io.quarkus.rest.client.reactive.ClientExceptionMapper и jakarta.ws.rs.core.Response. null означает «обработать как обычно». Само исключение и обработчик для сервера пишутся так же, как PostNotFoundException:
// src/main/java/com/example/blog/AuthorNotFoundException.java
package com.example.blog;
public class AuthorNotFoundException extends RuntimeException {
public AuthorNotFoundException() {
super("Автор не найден");
}
}// src/main/java/com/example/blog/ExceptionMappers.java
@ServerExceptionMapper
public RestResponse<ErrorMessage> mapAuthorNotFound(AuthorNotFoundException e) {
return RestResponse.status(Response.Status.NOT_FOUND, new ErrorMessage(e.getMessage()));
}Теперь curl -i http://localhost:8080/authors/999 возвращает 404 и {"error":"Автор не найден"}.
Частая ошибка
Без @ClientExceptionMapper ошибка чужого сервиса превращается у нас в 500:
HTTP/1.1 500 Internal Server Error
А в логе:
ERROR [io.quarkus.vertx.http.runtime.QuarkusErrorHandler] HTTP Request to /authors/999 failed, error id: ...:
org.jboss.resteasy.reactive.ClientWebApplicationException: Received: 'Not Found, status code 404'
when invoking REST Client method: 'com.example.blog.UserClient#getById'
Quarkus намеренно не передаёт клиенту статус удалённого сервиса как есть: чужой 404 не означает, что не найден адрес нашего API. Решайте сами, что ответить, через @ClientExceptionMapper или try/catch вокруг вызова.