Octoping의 블로그
article thumbnail

RestTemplate의 시대가 저물다

Spring은 3.0버전부터, 간편하게 HTTP 통신을 할 수 있는 RestTemplate라는 내장 객체를 선보였다.

이 RestTemplate은 멀티 쓰레드 방식을 사용하고, Blocking 방식을 사용한다.

 

이 RestTemplate는 참 오랜 시간동안 잘 쓰였지만 바로 뒤에 말할 WebClient의 등장 이후 RestTemplate는 유지관리(maintenance) 모드에 들어가게 되었다.

 

(이와 관련된 해프닝으로 RestTemplate이 deprecated 되었다는 얘기도 쫙 돌았었는데, 관련한 내용은 토비님께서 잘 정리해두신 영상이 있다: https://youtu.be/S4W3cJOuLrU )

 


WebClient와 WebFlux

WebClient는 Spring이 5.0버전에서 WebFlux와 함께 새로 내놓은 인터페이스다.

WebClient는 싱글 스레드 방식을 사용하고, Non-Blocking 방식을 사용한다는 차이점이 있다.

 

추가적으로 webflux라는 의존성 안에 들어있는 녀석이기 때문에 다음과 같은 의존성을 따로 추가해주어야 한다.

implementation 'org.springframework.boot:spring-boot-starter-webflux'

 

코드 생김새의 차이

WebClient는 다음과 같은 방식으로 모던한(?) 체이닝 방식으로 API를 호출한다.

WebClient.create("http://localhost:8080")
        .post()
        .uri("/")
        .accept(MediaType.APPLICATION_JSON)
        .exchangeToMono(response -> response)
        .block();

 

RestTemplate와 비교해보면 역시 WebClient 쪽이 조금 더 확실히 깔끔하다고 느껴진다.

// RestTemplate
RestTemplate restTemplate = new RestTemplate();
HttpHeaders headers = new HttpHeaders();
MediaType mediaType = new MediaType("application", "json", StandardCharsets.UTF_8);
headers.setContentType(mediaType);
HttpEntity<String> entity = new HttpEntity<>("{}", headers);

String response = restTemplate.postForObject("http://localhost:8080", entity, String.class);

 

역시 새로 나온 WebClient가 옛날 녀석인 RestTemplate보다 코드 생김새로 보면 좋아보인다!

 

하지만 결국 근본이 WebFlux..

하지만 문제가 있으니. WebClient는 WebFlux에 소속된 녀석이다.

 

WebFlux는 배달의 민족의 가게 메뉴 조회 페이지처럼, 순수하게 조회 관련 IO가 로직의 대부분을 차지하는 느낌의 서비스의 경우에나 들어가는 특수적인 스펙이다보니 많이 쓰이는 녀석은 아니다.

 

그렇기 때문에 일반적으로 Spring MVC를 사용하는 프로젝트의 경우, 이 API 호출기 하나만을 위해 필요 없는 WebFlux라는 의존성을 굳이 설치해야 한다는 불ㅡ편 포인트가 생긴다.

 

추가로 WebClient는 기본적으로 Mono와 Flux라는 WebFlux의 타입들을 반환하는데, 이 녀석을 Spring MVC에서 사용해먹기가 영 불편할 따름이 아닐 수 없다. Mono와 Flux의 결과값을 다루기 위해서는 subscribe을 통해 콜백 함수로 받아 사용하거나, map을 사용해야 하는데, 이런 활용 방식은 기존에 RestTemplate을 사용하던 방식과는 너무나도 달라 불편하다.

 

물론 block이라는 메소드를 사용하면 원하는 객체 그대로 받을 수 있지만, 어쩜 이리 불길하게도 block을 사용하는 것은 WebClient 안티 패턴이라고 스프링 공식문서에서 친히 경고하고 있다.

 

"Flux와 Mono를 사용하면, Spring MVC에서 절대(never) block할 필요가 없습니다"

 

WebFlux를 사용하는 리액티브 프로그래밍 환경이라면 block 하지 않는 것이 당연한 이야기겠지만.. Spring MVC에서 block을 쓰지 않고 Mono와 Flux를 그대로 다루라는 것은 좀 너무한 것처럼까지 느껴진다.

 

그냥 공식문서 무시하고 block 쓰고 말란다.

 


지나가며 살펴보는 막간, HTTP Interface

그런 와중에 스프링은 6.0으로 올라가면서 HTTP Interface라는 녀석을 또 세상에 내놓았다.

 

얘는 정확히는 앞에서의 RestTemplate이나 WebClient처럼 API를 직접 쏠 수 있는 녀석은 아니고.. Java의 인터페이스를 이용해서 선언적으로 HTTP 서비스를 정의할 수 있는 녀석이다. (Feign이라는 라이브러리에서 영감을 받았다고 한다)

 

interface BooksService {

    @GetExchange("/books")
    List<Book> getBooks();

    @GetExchange("/books/{id}")
    Book getBook(@PathVariable long id);

    @PostExchange("/books")
    Book saveBook(@RequestBody Book book);

    @DeleteExchange("/books/{id}")
    ResponseEntity<Void> deleteBook(@PathVariable long id);
}

다음과 같이 인터페이스를 작성하자.

그런 다음 이제 실제로 HTTP Exchange를 할 프록시를 생성해야 한다.

 

HttpServiceProxyFactory httpServiceProxyFactory = HttpServiceProxyFactory
  .builder(WebClientAdapter.forClient(webClient))
  .build();
  
booksService = httpServiceProxyFactory.createClient(BooksService.class);

이렇게 프록시 팩토리를 만들어주고 프록시 인스턴스를 @Bean이나 @Component로 만들어주면 끝난다.

 

 

뭐 결국 얘 자체가 API를 쏘는 애는 아니다보니 주제랑 딱 맞지는 않지만, 그래도 API 쏠 때 써볼 수 있는 녀석이니 같이 소개해보았다.

 

더 자세히 알아보려면..

https://www.baeldung.com/spring-6-http-interface

https://youtu.be/Kb37Q5GCyZs

 


Spring 6.1에서 새로 나온 RestClient

https://spring.io/blog/2023/07/13/new-in-spring-6-1-restclient

 

New in Spring 6.1: RestClient

Spring Framework 6.1 M2 introduces the RestClient, a new synchronous HTTP client. As the name suggests, RestClient offers the fluent API of WebClient with the infrastructure of RestTemplate. Fourteen years ago, when RestTemplate was introduced in Spring Fr

spring.io

짜잔, 이제 이 글을 쓴 진짜 주제가 나왔다.

Spring 6.1부터 새롭게 등장한 RestClient라는 친구다.

 

WebClient의 fluent API를 그대로 해서 RestTemplate의 인프라와 함께 사용할 수 있다!!

 

RestClient restClient = RestClient.create();

String result = restClient.get()
  .uri("https://example.com")
  .retrieve()
  .body(String.class);
  
System.out.println(result);

생긴걸 보면 역시 WebClient처럼 깔끔한 생김새가 마음에 든다.

 

// POST
Pet pet = ...
ResponseEntity<Void> response = restClient.post()
  .uri("https://petclinic.example.com/pets/new")
  .contentType(APPLICATION_JSON)
  .body(pet)
  .retrieve()
  .toBodilessEntity();
  
  
  // 에러 핸들링
  String result = restClient.get()
  .uri("https://example.com/this-url-does-not-exist")
  .retrieve()
  .onStatus(HttpStatusCode::is4xxClientError, (request, response) -> {
      throw new MyCustomRuntimeException(response.getStatusCode(), response.getHeaders())
  })
  .body(String.class);

 

 

등등.. 야무진 기능들이 많다.

 

거기에 RestClient에는 WebFlux 의존성도 필요 없기 때문에 위에서의 불편함 없이 편ㅡ안하게 사용할 수 있을 것 같다.

 


마무리

RestClient는 3.2.0 M1 버전 부터 출시된다고 하는데, 이 RestClient의 미래가 기대된다.

아자아자 파이팅

profile

Octoping의 블로그

@Octoping

포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!