본문 바로가기
Spring

WebClient 사용할때 주의 (4편)

by ybs 2021. 11. 5.
반응형

이전 WebClient 글 예제 코드로 아래와 같이 clientResponse.createException().flatMap() 과 onErrorResume 을 사용했는데 flatMap 을 해서 다시 Mono.error 로 감싸는, 어떻게 보면 비효율적인 코드다. 

 

AS-IS

.retrieve()
.onStatus(
  httpStatus -> httpStatus != HttpStatus.OK,
    clientResponse -> {
      return clientResponse.createException()
         .flatMap(it -> Mono.error(new RuntimeException("code : " + clientResponse.statusCode())));
      })
  .bodyToMono(String.class)
  .onErrorResume(throwable -> {
    return Mono.error(new RuntimeException(throwable));
});

 

그래서 아래와 같이 flatMap 은 map으로, onErrorResume 은 onErrorMap 으로 바꿔서 Mono.error 로 wrapping 한거를 없앨 수 있다.

 

TO-BE

.retrieve()
.onStatus(
  httpStatus -> httpStatus != HttpStatus.OK,
    clientResponse -> {
      return clientResponse.createException()
         .map(it -> new RuntimeException("code : " + clientResponse.statusCode()));
      })
  .bodyToMono(String.class)
  .onErrorMap(throwable -> {
    return new RuntimeException(throwable);
});

 

그런데 주의할점이 있다. exchangeToMono나 exchangeToFlux 메서드에서는 flatMap을 유지해야 한다. 아래 코드와 같이 map으로 하게 되면 리턴타입이 Mono<RuntimeException> 이 되고 정상적인 리턴타입인 Mono<String> 과 안맞아 컴파일 에러가 발생한다.

.exchangeToMono(clientResponse -> {
	if (clientResponse.rawStatusCode() == HttpStatus.BAD_REQUEST.value()) {
    
		// Mono<RuntimeException> type. 이걸 리턴하면 컴파일 에러발생
		clientResponse
			.createException()
			.map(it -> new RuntimeException("400!!"));

		// Mono<String> type
		return clientResponse
			.createException()
			.flatMap(it -> Mono.error(new RuntimeException("400!!")));
	}

	if (clientResponse.rawStatusCode() == HttpStatus.INTERNAL_SERVER_ERROR.value()) {
		return clientResponse
			.createException()
			.flatMap(it -> Mono.error(new RuntimeException("500!!")));
	}

	// Mono<String> type
	return clientResponse.bodyToMono(String.class);
})

 

cf) Mono.error 는 원본 T 타입을 유지시키므로 Mono<String> 리턴타입이 나온다.

public static <T> Mono<T> error(Throwable error) {
	return onAssembly(new MonoError(error));
}

 

마지막으로 onErrorMap은 아래 코드와 같이 활용하는것도 가능하다. WebClient 요청이 Timeout 으로 실패했을 때만 retry 를 수행하는데 3번다 실패하면 RetryExhaustedException 이 발생한다. 그래서 RetryExhaustedException 일때만 getCause(한꺼풀 벗겨내서) 를 뒤로 전달한다.

.retryWhen(
	Retry
		.fixedDelay(3, Duration.ofMillis(500))
		.filter(YBSUtils::isTimeoutError)
)
.onErrorMap(
	Exceptions::isRetryExhausted,
	Throwable::getCause
)
...
반응형