RestTemplate를 RestClient로 ?!
현재 프로젝트에서는 GPT와의 통신이 필수인만큼 외부 API를 꼭 사용해야 한다.
그렇기에 이전에도 잘 써왔던 RestTemplate를 적용해서 프로젝트를 진행하고 있었다.
전혀 아무 문제 없이 외부 API를 호출했고 원하는 대로 기능완성도 되었다.
평범한 일상을 보내던 중 자극적인 문장 하나를 보게 되었는데...
"RestTemplate은 곧 deprecated 된다" 이걸 보자마자 머리가 띵했다. (나 잘 쓰고 있는데 왜?)
그렇게 급한 마음에 좀 찾아보니 현재 WebClient라는 기술이 잘 사용되고 있고 RestTemplate은 유지보수 단계에 들어갔다고 한다.
나도 최신기술을 도입해보고자 하는 마음에 WebClient 기술을 간단하게나마 공부해 봤다.
Webclient는 Spring5.0에서 WebFlux와 함께 내놓은 인터페이스이며 특징으로는 싱글 스레드 방식을 사용하고 Non-Blocking 방식을 사용한다는 것이다. 추가로 Mono와 Flux라는 타입을 사용한다.
사용하기 위해서는 아래의 의존성을 추가해 주면 된다.
implementation 'org.springframework.boot:spring-boot-starter-webflux'
추가를 해주면 되는데..... 난 너무 마음에 안 들었다.
내 프로젝트의 경우 GPT, 카카오, 자체 개발한 평가서버(FastAPI) 총 3번의 외부 API 호출이 있다. (아주 간단한 호출)
아주 간단한 호출인데 말로만 들었던 WebFlux 산하인 Webclient를 사용해야 한다?
이 또한 부담스럽지만, 일단 RestTemplate과 사용방식이 너무 달랐다. (WebFlux를 공부하기엔 시간적으로 쫓겼다.. 졸작.....)
그렇게 하염없이 뭐 좋은 거 없나 찾아보다가 아주 마음에 드는 녀석을 찾게 되었다.
오늘의 주인공인 RestClient이다.
RestClient
RestClient는 Spring6.1에서 내놓은 아주 따끈따끈하다고 볼 수 있는 녀석이다.
https://docs.spring.io/spring-framework/reference/integration/rest-clients.html
Webclient와 마찬가지로 Fluent API를 제공해서 코드를 더 간결하고 직관적으로 만들 수 있다.
예시로 Get 코드를 봐보자.
RestClient restClient = RestClient.create();
String result = restClient.get()
.uri("https://example.com")
.retrieve()
.body(String.class);
System.out.println(result);
아주 맘에 든다. 거기다 더 마음에 드는 건 아래 두 가지다.
- 새로운 의존성을 추가할 필요가 없다.
- 기존 RestTemplate의 인프라와 함께 사용이 가능하다.
내가 Webclient를 쓰기 싫어하던 이유 두 가지를 바로 해결해 주었다.
번외로 Webclient에서 지원하는 Non-blocking을 RestClient에서는 못할 줄 알고 찾아봤는데 역시나 당연히 되더라.. Non-blocking이 필요하더라도 RestClient를 쓰면 될 거 같다.
RestTemplate To RestClient
코드 예시로 GPT와 통신하는 코드를 보여주겠다!
[ 기존 ]
// GPTRestTempConfig.java
@Bean
@Qualifier("gptRestTemplate")
public RestTemplate gptRestTemplate() {
RestTemplate restTemplate = new RestTemplate();
restTemplate.getInterceptors().add((request, body, execution) -> {
request.getHeaders().add("Authorization", "Bearer " + secretKey);
return execution.execute(request, body);
});
return restTemplate;
}
// GPTServiceImpl.java
...
@Qualifier("gptRestTemplate")
@Autowired
private final RestTemplate gptRestTemplate;
@Override
public String getNewQuestion(ChatRequest chatRequest) {
ChatResponse gptResponse = gptRestTemplate.postForObject(apiUrl, chatRequest, ChatResponse.class);
ChatResponse gptResponse = gptComponent.getGptResponse(chatRequest);
checkValidGptResponse(gptResponse);
return gptResponse.getChoices().get(0).getMessage().getContent();
}
...
기존에 사용하던 방식이다. 사용했던 사람들은 코드만 봐도 익숙할 것이다. 핵심은 postForObject!
이제 이를 바꿔보자.
[ RestClient 적용 ]
@Configuration
public class RestClientConfig {
@Value("${openai.url}")
private String gptUrl;
@Value("${openai.secret-key}")
private String gptSecret;
@Bean
public GPTComponent gptComponent() {
RestClient restClient = RestClient.builder()
.baseUrl(gptUrl)
.defaultHeader("Authorization", "Bearer " + gptSecret)
.build();
RestClientAdapter adapter = RestClientAdapter.create(restClient);
HttpServiceProxyFactory factory = HttpServiceProxyFactory.builderFor(adapter).build();
return factory.createClient(GPTComponent.class);
}
}
@Component
@HttpExchange
public interface GPTComponent {
@PostExchange()
ChatResponse getGptResponse(@RequestBody ChatRequest request);
}
// GPTServiceImpl.java
private final GPTComponent gptComponent;
@Override
public String getNewQuestion(ChatRequest chatRequest) {
// 위의 코드에서
ChatResponse gptResponse = gptRestTemplate.postForObject(apiUrl, chatRequest, ChatResponse.class);
// 아래로 바뀌었다. 서비스상에서는 큰 차이가 보이지 않는다.
ChatResponse gptResponse = gptComponent.getGptResponse(chatRequest);
...
}
GPTComponent 파일을 보면 아주 대박이다. 기존 RestTemplate를 사용할 때는 postForObject 메서드를 직접 이용해서 post 요청을 보냈지만 RestClient에서는 단순히 인터페이스를 정의해서 동일한 역할을 수행하게 된다.
이 방식은 FeignClient가 떠오르기도 한다.
앞으로는 RestTemplate보다 RestClient를 사용할 것 같다. WebClient의 편한 방식을 어쩜 이리 잘 가져왔는지....
무거운 WebClient 대신 RestClient를 사용해 보는 게 어떨까?!
일단 이 유튜브 영상을 보고 생각해도 늦지 않다. 너무 좋은 영상이다. (토비 님 짱짱)