22년 초에 restTemplate을 사용하는 프로젝트를 작업하다가 위 문구를 보게 되었다.
음..? 잘 쓰고 있던 rest template이 deprecated 된다고?
그래서 그 이후에 신규로 진행하는 프로젝트는 webClient를 사용하였다.
왜 webClient를 사용하였냐고 물으신다면, 위에서처럼 굳이 spring java doc에 대체하여 쓰라고 할 정도니, 스프링 진영에서 정식으로 밀고 있는 것이라 생각했기 때문이다(곧 대세가 될 것이라 생각했다).
참고로 webClient는 springframework 5에 추가된 것으로 기본적으로 reactive, non-blocking 요청을 지원한다(그래서 응답이 Mono, Flux 등으로 온다).
무튼 그렇게 webClient를 신규 프로젝트들에서 사용하게 되는데, 설정과 사용 시 상당한 라인의 코드가 필요한 것을 깨닫게 되었다.
공통 설정을 빈에 등록하는 코드, 그걸 가져와서 서비스마다 주입을 하고, 주입된 webClient로 get/post 등의 요청을 하는데도 상당한 코드가 필요하다.
물론 공통화하여 사용하고 있기는 하지만 외부 api가 새로 추가할 때마다 비슷한 양을 추가해야 한다.
사실 처음에는 webClient를 사용함으로써 webFlux에 친숙해지고, 궁극적으로는 non-blocking 요청에 대한 친근감(..)이 생기지 않을까 하는 마음이 컸다. 하지만 업무에서는 실질적으로 동기 요청이 훨씬 많았고, 이를 위해 억지로 mono.block()을 하고 있어 코드 양만 늘어난 샘이 되었다.. 결국 제대로 활용하지 못하고 있다는 생각이 들었다.
그렇게 시간이 지나고 22년 11월 springframework6이 정식(GA) 출시하면서 진짜 restTemplate에 @Deprecated가 달렸는지 궁금해졌다.
그런데 새로운 프래임워크를 열어보기도 전, 현재 사용하는 프로젝트(springboot2.7.3; springframework 5.3)에서 먼저 확인하니 안내 문구가 바뀌어져 있었다?
(확인해 보니 springframework3, 4에는 안내하는 javadoc 조차 없음)
NOTE: As of 5.0 this class is in maintenance mode, with only minor requests for changes and bugs to be accepted going forward. Please, consider using the org.springframework.web.reactive.client.WebClient which has a more modern API and supports sync, async, and streaming scenarios.
webClient를 추천하는 문구는 그대로인데.. deprecated 된다는 말은 쏙 빠지고, 유지보수모드(간단한 버그수정만 지원)로 전환한다는 말로 바뀌어져 있었다. 요 녀석들.. 고도의 밑장 빼기인가..
위에서 webClient를 사용하면서도 굳이 이걸 써야하는가? 다음 프로젝트에서도 또 webClient를 쓸 것인가? 대해 의문을 가지고 있었는데.. 마침 springframework6 문서에서 Http interface에 대한 글을 보게 된다.
Integration
The Spring Framework provides abstractions for the asynchronous execution and scheduling of tasks with the TaskExecutor and TaskScheduler interfaces, respectively. Spring also features implementations of those interfaces that support thread pools or delega
docs.spring.io
얼핏 보니 생김새는 feign과 비슷하고 내부는 webClient로 되어 있는 듯하다.
쓱싹 만들어본다.
아래와 같이 세팅하고 받아준다.
http interface 는 webClient 기반이라 webflux dependency가 필요하다.
implementation 'org.springframework.boot:spring-boot-starter-webflux'
참고로 springboot3은 gradle7.6, java17 기반이니 11을 기본으로 사용하였다면 꼭 설정을 바꿔야지 아니면 아래와 같은 에러를 만난다.
1. api의 정보가 담긴 interface를 아래와 같이 만든다.
@HttpExchange(url = "/api/arena-ring")
public interface GiaArenaRingService {
@GetExchange("/{id}")
Map<String, Object> getArenaRingGame(@PathVariable BigInteger id);
}
2. 이 interface의 구현체는 스프링으로 부터 자동으로 생성되는데, 아래와 같이 빈을 등록해야 한다.
@Configuration
public class GiaHttpConfig {
@Bean
GiaArenaRingService getGiaArenaRingService(@Value("${gia-aapoker-dev}") String url) {
WebClient client = WebClient.builder().baseUrl(url).build();
HttpServiceProxyFactory factory = HttpServiceProxyFactory.builder(WebClientAdapter.forClient(client)).build();
GiaArenaRingService service = factory.createClient(GiaArenaRingService.class);
return service;
}
}
3. 사용하고자 하는 곳에서 이 빈을 주입한 후 해당 함수를 호출하면 된다.
@Service
@RequiredArgsConstructor
public class ExternalService {
private final GiaArenaRingService giaArenaRingService;
public Map<String, Object> getArenaRingGameHttpInterface(BigInteger id) {
return giaArenaRingService.getArenaRingGame(id);
}
}
끝.
예시는 바로 객체를 꺼내 오도록 했으나 기존의 webClient처럼 Mono나 Flux로 반환하게끔 할 수도 있다(support both blocking and reactive return values).
사용법이 간단하고 api 스펙을 interface로 선언하기만 하면 되어 한눈에 볼 수 있다는 장점이 있는 것 같다.
webClient 기반이라 기존 webClient에서 지원하던 기능들은 설정방식만 조금 다를 뿐 다 지원할 듯하다.
가독성이 떨어지고 코드의 양이 많았던 webClient의 단점을 어느 정도 보완해 줄 수 있을 것 같아 기회가 되면 사용해 볼 생각.
추가 가이드: https://www.baeldung.com/spring-6-http-interface
++ 더불어
restTemplate, webClient 이 아직도 건재하다는 소식에 힘입어 세 방법 모두 사용해 본다.
(restTemplate와 webClient를 공통 빈으로 등록하면 효율성과 가독성의 측면이 더 좋겠으나 샘플 프로젝트이므로 생략)
//using WebClient
public Mono<Map<String, Object>> getArenaRingGameWebClient(BigInteger id) {
return WebClient.create(GIA_URL).get().uri(uriBuilder -> uriBuilder.path(STATIC_ARENA_RING_GAME_URI + "/" + id).build()).retrieve()
// .onStatus(HttpStatus::isError, resp -> Mono.error(new RuntimeException("status is not 200")))
.bodyToMono(new ParameterizedTypeReference<Map<String, Object>>() {
}).doOnNext(response -> Optional.ofNullable(response).orElseThrow(() -> new RuntimeException("body is null")));
}
//using RestTemplate
public Map<String, Object> getArenaRingGameRestTemplate(BigInteger id) {
String url = GIA_URL + STATIC_ARENA_RING_GAME_URI + "/" + id;
RestTemplate restTemplate = new RestTemplate();
return restTemplate.getForObject(url, Map.class);
}
//using httpClient
public Map<String, Object> getArenaRingGameHttpInterface(BigInteger id) {
return giaArenaRingService.getArenaRingGame(id);
}
restTemplate: 2
webClient: 2
httpClient: 2
세 건 모두 잘 된다.
끝으로.
springboot2.x 를 사용해 본 유저라면 누구든 springboot3을 사용하고 싶어 할 것이다.
관련 migration guide가 있으니 springboot3을 사용하기 전 뭐가 달라졌는지 간단히 살펴보는 것이 좋겠다.
https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-3.0-Migration-Guide
참고:
https://spring.io/blog/2022/11/16/spring-framework-6-0-goes-ga
Spring | Home
Cloud Your code, any cloud—we’ve got you covered. Connect and scale your services, whatever your platform.
spring.io
'개발 > spring' 카테고리의 다른 글
[spring-jpa] N+1 문제 관련 jpa 돌아보기 -2 (0) | 2023.03.15 |
---|---|
[spring-jpa] N+1 문제 관련 jpa 돌아보기 -1 (0) | 2023.03.14 |
[springboottest] 테스트에서 h2-console 사용하기 (0) | 2023.02.23 |
ModelAttribute vs RequestBody data bind, 직렬화 & 역직렬화 (0) | 2023.02.21 |
[java-springboot] 테스트 코드 종류 (0) | 2023.02.17 |