-
Spring RestClient기술/스프링 2024. 3. 24. 20:19
요약
스프링 6.1(스프링 부트 3.2)에서 Http 호출을 위한 새로운 인터페이스인 RestClient 출시
- 동기식 Http 호출을 Web Client와 동일한 Fluent Style API로 사용 가능
- 아파치5 / Jdk / Jetty / SimpleClient(jdk 1.11 미만)을 HttpClient 라이브러리를 백본으로 지원RestClient
최근 스프링 부트 3.2 (스프링 6.1) 버전이 공개되면서 Http 호출을 위한 새로운 인터페이스인 RestClient이 나왔다. RestClient는 개발자가 Http 호출을 더 간편하게 할 수 있도록 설계되었으며, 스프링에서 제공하던 RestTemplate과 WebClient에 이은 세 번째 옵션이다.
배경
태초엔 RestTemplate이 있었지만... RestTemplate의 동기식 호출 방식으로 인한 성능 문제 / 복잡한 인터페이스를 해결하기 위해,
어질어질한 RestTemplate의 인터페이스
스프링 진영은 Spring 5.0에서 WebClient를 공개했다. WebClient는- 리액티브 스트림을 통한 비동기 호출을 제공하고
- 요청 및 응답을 커스터마이즈하기 훨씬 쉬운 개선된 API를 제공한다. (Fluecnt Style API)
다만, WebClient는 Servlet 기반의 프로젝트에서 사용 시 불필요한 코드 (runBlocking, bodyToMono.block() 등)이 필요하고, Webflux 의존성이 추가되어야한다는 단점이 남아있다.
그리고 프로젝트 Loom의 등장으로 비동기식 API를 사용하지 않고도 충분한 성능을 달성할 수 있는 환경이 만들어졌다. 그래서 기존 WebClient의 fluentAPI를 유지하되, 동기식 API를 제공하는 RestClient가 스프링 6.1에서 등장했다.간단 예제 코드
RestClient는 WebClient와 거의 동일한 인터페이스를 통해 사용할 수 있다.
다음은 Http 호출을 위해 필요한 Query Param, Path Variable, Header, Body 등을 설정한 간단한 예제 코드이다.
fun postExample() { val restClient = RestClient.builder() .baseUrl("http://localhost:8082") .build() val pathVariable = 1 val responseBody = restClient.post() .uri { it.path("/echo/$pathVariable") .queryParam("hello", "hello") .build() } .header("TEST", "test-header") .body( mapOf("body" to "body") ) .retrieve() .body<String>() }
RestTemplate을 사용할 때도 수신 객체 지정 함수인 run, let을 통해 비슷한 느낌을 낼 수 있지만, RestClient가 제공하는 API가 더 직관적이고 깔끔하다.fun postExample() { val restTemplate = RestTemplateBuilder() .rootUri("http://localhost:8082") .build() val pathVariable = 1 val responseBody = HttpEntity( mapOf("body" to "body"), HttpHeaders().apply { set("TEST", "test-header") } ).run { restTemplate.exchange( "/echo/$pathVariable?hello=hello", HttpMethod.POST, this, String::class.java ) }.let { responseEntity -> checkNotNull(responseEntity.body) } }
Rest Client의 Response Handling 예제// Body만 필요한 경우 val responseBody = restClient.get() .uri("/echo") .retrieve() .body<String>() // 응답 헤더 등이 필요한 경우 val responseEntity = restClient.get() .uri("/echo") .retrieve() .toEntity<String>() // 응답 코드에 따라 에러 핸들링이 필요한 경우 val handlerAddResponseBody = restClient.get() .uri("/echo") .retrieve() .onStatus(HttpStatusCode::is4xxClientError) { _, response -> throw IllegalStateException("${response.statusCode}") } .onStatus(HttpStatusCode::is5xxServerError) { _, response -> throw IllegalStateException("${response.statusCode}") } .body<String>() // 더 세세한 응답 값 핸들링 val exchangeResponseBody = restClient.get() .uri("/echo") .exchange { _, response -> when(response.statusCode) { HttpStatusCode::is4xxClientError -> throw IllegalStateException("${response.statusCode}") HttpStatusCode::is5xxServerError -> throw IllegalStateException("5xx") else -> response.bodyTo(String::class.java) } }
스프링 기반 프로젝트를 처음 접한 회사 소스에서 WebClient를 사용하고 있어서 그런지, WebClient/RestClient 스타일이 직관적이고 깔끔하고, 눈에 익는다.
그리고 Servlet 기반 프로젝트에 runBlocking / Mono 등 불필요한 Webflux API 의존성을 가져갈 필요도 없다는 건 훌륭해 보인다.
심지어 친절한 스프링 진영에서는 RestTemplate에서 RestClient로의 마이그레이션 가이드까지 제공해준다. 다만, 굳이 잘 쓰고 있으면 안가도 되긴 한다.
참고로, RestClient에서 실제 통신을 담당하는 Http 라이브러리는 아파치5 / Jdk / Netty / SimpleClient(jdk 1.11 미만) 등이 지원된다.한계
하지만, 서블릿 기반 프로젝트에서 WebClient를 사용하던 걸 전부 RestClient로 넘어가야하는가? 에 대해서는 아직 의문부호가 있다.
예를 들어, 여러 API를 병렬 호출해서 이를 모아서 프론트에 전달해주는 API가 있다고 생각해보자. 기존의 WebClient 사용 환경에서는 코루틴을 활용해 병렬 호출을 쉽게 할 수 있지만, RestClient를 사용할 경우, 코루틴 실행 쓰레드를 블록하기 때문에 문제가 발생할 수 있다.Virtual Thread를 사용한다던가... 하는 방식으로 해결할 수 있게지만, Kotlin 환경에서는 WebClient + 코루틴이 지금은 더 간단한 해결책으로 느껴진다.
더 읽어보면 좋은 글
공식문서
RestTemplate vs WebClient vs WebClient
Http Client 비교
비동기식 Http Client 비교'기술 > 스프링' 카테고리의 다른 글
Spring of LLM, Spring AI #1 - 소개 (0) 2024.03.17