카테고리 없음

[Spring] WebClient 복붙으로 간단하게 get, post 적용해보기

한희성 2023. 8. 8.
반응형

개요 

아직도 수많은 기업에서 레거시 소스들이 존재한다. IT시대가 짙어지면서 트래픽도 10년 전과는 차원이 다르고, 애플리케이션의 성능이 중요한 세상이 되었다.

이번 포스팅에서는 Spring5의 WebFlux 프레임워크 기반의 WebClient 기술을 포스팅하려고 합니다.

간단하게 복붙으로 WebClient를 붙일 수 있도록 코드와 패키지를 구성해 두었습니다.

 

프로젝트 구성 요소

1. IntelliJ IDE

2. Spring Boot 2.7.x

3. Gradle 8.1.x

4. JDK corretto 11

5. MacBook Pro 13 m2

 

1) 의존성 추가

메이븐 링크

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

 

2) 테스트 API 구성

패키지
co.kr.heeseong.god.webclient

하단에 설명 예정인 코드를 구동하기 위한 테스트 테이터가 담긴 API를 로컬에 만들어 두었습니다.

/accounts/{{id}}-get - 유저 1명 조회

/accounts/{{id}}-post - 유저 1명 업데이트

 

3) WebClient 객체 생성하기

private static WebClient webClient;
public WebClientTemplate(WebClient.Builder builder) {
    webClient = builder.build();
}
  • WebClient 객체는 이미 구현된 정적 팩토리 메서드를 이용해 uri를 생성할 때 넣어주거나 설정 없이도 생성할 수 있습니다.
  • 객체를 미리 생성해 두기 위한 기본 세팅 값은 공식 문서를 보는것이 가장 좋다.

4) WebClient 사용하기

private static <T> T webClientGetRequest(String uri, Map<String, String> headerInfo, Map<String, String> requestData, Class<T> responseType) {
        log.info("==================== webClient get start ====================");

        T response = null;
        try {
            response = webClient
                    .get()
                    .uri(uri + "?" + setQueryParam(requestData)) // uriBuilder 와 MultiValueMap 으로 처리가 가능하나.. 어째서인지 로컬에서 주소가 씹힘, 인코딩 이슈 또는 baseUrl 이슈인듯
                    .headers(httpHeaders -> {
                        for (String key : headerInfo.keySet()) {
                            httpHeaders.add(key, headerInfo.get(key));
                        }
                    })
                    .retrieve()
                    .bodyToMono(responseType)
                    .doOnSuccess(res -> {
                        log.info("--- success : request uri [{}], response [{}]", uri, res);
                    })
                    .doOnError(throwable -> {
                        log.error("--- exception : request uri [{}], response [{}]", uri, throwable.getStackTrace());
                    })
                    .timeout(Duration.ofSeconds(TIMEOUT_SEC))
                    .block();
        } catch (Exception e) {
            log.error("기호에 맞게 처리");
        }

        log.info("==================== webClient get end ====================");
        return response;
    }
  • 응답 타입을 동적으로 받기 위해 Class<T>를 선언하여 bodyToMono의 값에 세팅해 주었습니다.
  • .get() get 요청
  • 메서드 대신 .method(HttpMethod) 타입으로 http메서드 동적 처리 가능
  • headers 헤더 데이터 셋팅 ex : ) JWT 토근 등
  • retrieve() vs exchage()
    • 검색해 보면 해당 내용에 대한 레퍼런스가 정말 많다.
    • retrieve() 메서드는 응답을 받아 body를 바로 디코딩하여 객체를 바로 사용할 수 있도록 미리 만들어둔 메서드
    • exchage() 응답의 상태값, 헤더등 retrieve() 보다는 좀 더 많은 정보를 다를 수 있는 메서드입니다.
    • 하지만 exchage()는 디테일한 컨트롤은 가능하지만 응답에 대한 모든 처리를 직접 처리하게 되면 메모리 누수 또는 성능 저하의 원인이 되기 때문에 스프링에서는 retrieve()를 권장하고 있습니다.
  • retrieve() 
    • 해당 메서드를 사용할 때는 응답 메서드로 toEntity(), .bodyToMono(), .bodyToFlux()를 사용할 수 있습니다.
    •  Mono 객체는 0-1개의 결과를 처리, Flux는 0-N개의 결과를 처리하는 객체입니다.
    • exchange()를 deprecated 되었기도 하고 메모리 누수로 인하여 5.3 버전부터는 exchageToMono, exchageToFlux로 변경되었습니다.
  • doOnSuccess : 서버에서 200을 응답했다면 해당 메서드에서 응답값 확인이 가능
  • doOnError : 서버의 응답이 200이 아닌 경우 해당 메서드에서 처리 가능
  • timeout : 타임아웃 설정
  • block : Mono Flux가 아닌 동기 방식으로 객체를 처리합니다.
    • Non-Blocking 방식으로 처리하기 위해 subscribe()를 사용하여 callback url를 지정하여 처리 가능합니다.

 

5) 개인 적인 구현에대한 의견

서버의 응답 스펙을 미리 알거나 혹은 서버 개발자와 커뮤니케이션이 원활하다면 스펙 변경에 대응이 빠를 듯하다. 하지만 개발하다 보면 변경사항에 대해 노티가 누락되거나 모든 API의 스펙이 동일하지 않다.

예제에 flux의 대한 설명이 없다. 왜냐? Mono(응답)의 타입을 String.class로 받아 우리쪽 함수까지는 데이터를 떨궈 놓는 게 더 파싱 하기가 쉽지 않을까 하는 생각이다.

 

응답 타입이 맞지않아 Decoding에 실패하게 되면 원인을 찾는데 시간이 꽤나 귀찮게 걸린다. 하여, String.class로 함수까지는 성공으로 찍어두고 해당 데이터를 ObjectMapper 또는 Gson 라이브러리를 이용하여 데이터를 변환하는 게 좀 더 서버에서 스펙이 변하는것에 대한 대처가 유연하지 않을까 하는 생각이다. 물론 이게 정답은 아닙니다! 

 

6) 마지막으로  마치며

본인의 환경의 맞게 소스는 재구성하되 왜 써야 하는지, 어떻게 써야 하는지 정확한 식별이 된 후에 사용하길 바란다.

외부 API통신이 많은 환경에서의 성능저하 원인을 개선하는데 비약적이진 않으나 조금이나마 개선의 도움이 되었으면 하는 바람으로 작성했습니다. 그럼 이만..

 

7) 전체 소스 링크(제목 클릭)

 

반응형

댓글

💲 추천 글