🌿 Spring WebFlux

WebClient를 알기 전, Spring WebFlux를 먼저 알 필요가 있다. 이유는 WebClient가 WebFlux 프레임워크에 포함되어 있는 WebClientReactor를 기반으로 한 HTTP 요청 클라이언트이기 때문이다.

 

WebFlux는 Spring Web MVC와 다르게 Spring 5에서 새롭게 추가된 비동기/논블로킹 방식의 프레임워크이다.

기존 Spring Web MVC의 Servlet API에선 거의 대부분의 API와 모듈이 동기 방식으로 제공 되어있다. 하지만 Netty와 같은 서버와의 연동을 위해선 비동기/논블로킹 환경을 제공해야 했고, 이를 위해 WebFlux가 만들어지게 된 것이다.

 

WebFlux의 주요 특징

1. 비동기 / 논블로킹

Spring MVC는 Multi-thread와 Blocking 방식으로, Thread Pool에 접속한 클라이언트들을 처리하고 Thread Pool이 꽉 찼을 시 다음 클라이언트는 Thread Pool이 비어 자신의 차례가 올 때까지 순차적으로 기다려야 한다.

반면, Spring WebFlux 같은 경우에는 Single-thread와 Non-Blocking 방식으로 클라이언트들은 다른 클라이언트의 작업이 끝나기를 기다릴 필요 없이 각 클라이언트들의 작업을 처리할 수 있는 것이다.

 

2. 리액티브 프로그래밍

클라이언트가 특정 요청을 보냄으로써 서버가 비로소 움직이는 것이 아니라, 데이터 흐름과 전달 변경에 반응하여 비동기적으로 데이터를 처리할 수 있다. RDBMS로 따지면 트리거 같은 것이라 생각한다. 새로운 데이터의 흐름이나 전달 변경에 대한 이벤트가 발생하면 스트림이 생성되고, 스트림을 구독하면 이벤트를 처리할 수 있다. 

 

Reactor

리액티브 프로그래밍을 하기 위해서는 핵심 라이브러리인 Reactor를 중점적으로 살펴보아야 한다. 

Reactor는 데이터 스트림을 표현하기 위해  Mono와 Flux라는 두 가지의 핵심 타입을 사용하는데, 이는 WebClient에도 사용되니 주의깊게 살펴보자.

1. Mono: 0부터 1개의 데이터 스트림을 처리할 수 있는 Publisher로, 단일 값이나 비어있는 값을 처리할 때 주로 사용한다.

2. Flux: 0부터 N개의 데이터 스트림을 처리할 수 있는 Publisher로, 여러 개의 데이터를 비동기적으로 처리할 때 주로 사용한다.

 

예를 들어 한 사용자의 정보를 ID로 구분하여 조회하고 싶을 때 Mono를 사용하고, 모든 사용자의 정보를 조회하고 싶을 때엔 Flux를 사용하면 된다는 것이다.

@RestController
public class UserController {
    @GetMapping("/user/{id}")
    public Mono<User> getUser(@PathVariable String id) {
        return userRepository.findById(id);
    }

    @GetMapping("/users")
    public Flux<User> getAllUsers() {
        return userRepository.findAll();
    }
}

🌿 Spring WebClient

이제 진짜 WebClient에 대해 살펴보자.

WebClient는 위에서 어렵게 설명했던 WebFlux의 일부로, 비동기 / 논블로킹의 웹 요청을 수행하기 위해 만들어졌다.

Spring에서 자주 사용되던 RestTemplate의 대안으로, Spring 또한 RestTemplate를 유지 모드로 돌릴테니 WebClient를 사용하라고 적극 권장하였다. (덕분에 RestTemplate을 잘 쓰고 있던 동기 방식의 요청이 필요한 프로젝트들이 WebFlux 라이브러리를 추가하고, .block()을 써 통신하게 되었다.)

 

WebClient의 주요 특징

1. 비동기 / 논블로킹

.subscribe() 체이닝에 콜백 함수를 작성하여 비동기 방식으로 응답 받은 데이터의 처리가 가능하다.

응답을 굳이 기다려야 할 필요가 없는 refresh 같은 경우에 사용하면 좋을 듯 하다.

 

2. 리액티브 프로그래밍

.bodyToMono().bodyToFlux()를 통해 Mono 타입과 Flux 타입으로 데이터를 반환하여, 리액티브 프로그래밍을 쉽게 적용시킬 수 있다.

 

WebClient 사용 예시

아래는 내가 현재 개발하고 있는 프로젝트에서 사용한 코드를 가져온 것이다.

public class RiotApiUtil {
    private final WebClient webClient;

    private RiotApiUtil(@Value("${api.riot.secretkey}") String apiKey) {
        webClient = WebClient.builder().baseUrl("https://asia.api.riotgames.com").defaultHeader("X-Riot-Token", apiKey).build();
    }

    public AccountDto requestPuuidByRiotId(String gameName, String tagLine) {
        return webClient.get().uri("/riot/account/v1/accounts/by-riot-id/{gameName}/{tagLine}", gameName, tagLine)
                .retrieve()
                .onStatus(HttpStatusCode::is4xxClientError, clientResponse -> { // 4XX 에러
                    throw new RecordException(ErrorMessage.RIOT_ID_NOT_FOUND);
                }).onStatus(HttpStatusCode::is5xxServerError, clientResponse -> {  // 5XX 에러
                    throw new RecordException(ErrorMessage.RIOT_API_FAILED);
                }).bodyToMono(AccountDto.class).block();
    }
}

 

Riot API를 사용하는 Util이므로, Riot API에 요청하는 URL 주소와 X-Riot-Token을 default header로 두었다.

요청하는 경우에는 WebClient 객체를 사용하여 추가 uri와 pathVariable을 통해 요청하고,

응답에 대해 onStatus로 처리 후 성공한 응답은 AccountDto에 매핑될 수 있도록 Mono로 두었다.

 

여기서 잠깐, 왜 .block()을 사용하였는가?

내가 개발한 방식은 요청을 보낸 즉시 사용자에게 응답을 보여주어야 하는 방식이라, 모든 순서가 동기 방식으로 이루어져야 했다. 하지만 Spring에서 WebClient로의 변경을 추천했기에 이런 식으로 비동기에 특화된 WebClient를 WebFlux 라이브러리까지 추가하며 동기 방식으로 사용할 수 밖에 없는 것이다. 

 

물론 동기 방식으로 WebClient를 사용하는 것에도 헤더나 쿼리 파라미터를 쉽게 조작 한다든지, 예외 관리가 더 쉽다든지 하는 개발자 편의성 관련 장점이 존재했다.


🌿 Spring RestClient

의미 없는 WebFlux를 적용한 이후, RestClient의 존재에 대해 알게 되었다. 왜 이제야 알았는지..!

Spring 6.1, SpringBoot 3.2에 새로운 동기식 HTTP 요청 클라이언트인 RestClient가 도입되었다.

 

RestTemplate을 대체하여 나온 WebClient는 반드시 WebFlux 라이브러리가 필요했고, 어려운 Reactor 방식을 불필요하게 학습해야 했다. 하지만, 동기 방식으로 완전히 RestTemplate을 대체할 수 있는 RestClient가 Spring 6.1에 도입된 것이다. 

 

RestClient의 주요 특징

1. 유연성

RestClient는 WebClient의 유연한 코드를 기반으로 만들어졌다. 그렇다면 이는 RestTemplate보다 헤더나 쿼리 파라미터를 쉽게 조작할 수 있고, 예외 관리가 쉬워진다는 것을 의미한다. 이 뿐만 아니라, 기존에 RestTemplate을 사용하고 있어서 전체 변경이 귀찮다면 기존의 RestTemplate 요소들 또한 사용할 수 있기 때문에 쉬운 호환 및 변경이 가능하다.

 

2. 인스턴스 생성의 내부적 처리

복잡한 설정을 위해 RestTemplateBuilder를 사용해야 했던 RestTemplate과 달리, RestClient는 RestClient.builder().build()를 통해 쉽게 인스턴스를 생성할 수 있다. 전통적이고 검증된 방식의 RestTemplate에 비해 RestClient 는 복잡한 설정을 내부적으로 처리해주어  HTTP 요청을 보내는 데 필요한 최소한의 코드만 작성하면 된다.


RestTemplate에서 RestClient로의 적용을 너무 막막하게만 생각하지 말고,

호환성이 충분히 있는 클라이언트니 한시라도 빠르게 적용하는 것이 다음으로의 프로젝트 확장에 더욱 도움이 될 수 있을 것 같다.

 

나도 이제 적용해보러...

유영웅