[Spring Boot] WebClient로 오픈 API 연동해보기

황성현·2024년 2월 17일
0

Spring Boot

목록 보기
1/2

WebClient를 사용하게 된 배경

  • 처음 진행하는 팀 프로젝트에서 실시간 지하철 위치를 가져와야하는 상황이 발생.
  • RestTemplate과 WebClient의 가장 큰 차이점은 "비동기화 가능" 과 "Non-Blocking처리 방식" 두 가지 중 하나를 선택해야했음.
  • 동시 사용자의 규모가 별로 없다면 RestTemplate를 사용해도 좋으나, 많아진다면 Webclient가 훨씬 처리속도가 빠름.
  • 현재 실시간 진행하고 있는 지하철 실시간 다음 역 보여주기 프로젝트는 출근 시간과 퇴근 시간에 몰릴 것으로 예상하여 WebClient를 사용하기로 결정.

Api Config

@Configuration
public class ApiConfig {

    @Bean
    public DefaultUriBuilderFactory builderFactory(){
        DefaultUriBuilderFactory factory = new DefaultUriBuilderFactory();
        factory.setEncodingMode(DefaultUriBuilderFactory.EncodingMode.VALUES_ONLY);
        return factory;
    }

    @Bean
    public WebClient webClient(){
        return WebClient.builder()
                .uriBuilderFactory(builderFactory())
                .build();
    }
}
  • Webclient는 WebClient.builder를 통해 객체 자유롭게 설정하며, 생성 가능
  • HTTP 요청을 할 때 service key를 queryParam으로 전달하게 되는데, WebClient가 queryParam을 UriComponentsBuilder.encode() 방식을 이용해서 인코딩하기 때문에 service key의 값이 달라져서 생기는 문제를 해결하기 위해 DefaultUriBuilderFactory를 만들고 encoding모드를 지정해주고 WebClient에 넣어줬음

ApiController

@RestController
public class ApiController {
    private final ApiService apiService;

    public ApiController(ApiService apiService) {
        this.apiService = apiService;
    }

    @GetMapping("/getApi")
    public RealtimeSubwayResponseDto getSubwayApi() {
        return apiService.createSubwayDto();
    }
}

ApiService

@Service
public class ApiService {
    private static final String REQUEST_HOST = "swopenAPI.seoul.go.kr";
    private static final String REQUEST_PATH = "/api/subway/{KEY}/{TYPE}/{SERVICE}/{START_INDEX}/{END_INDEX}/{subwayNm}";
    private static final String KEY = "624645505063796e3439644e434e47";
    private static final String TYPE = "json";
    private static final String SERVICE = "realtimePosition";
    private static final int START_INDEX = 0;
    private static final int END_INDEX = 5;
    private static final String subwayNm = "1호선";

    private final WebClient webClient;

    public ApiService(final WebClient webClient) {
        this.webClient = webClient;
    }

    public RealtimeSubwayResponseDto createSubwayDto() {
        return webClient.get()
                .uri(
                        UriComponentsBuilder.newInstance()
                                .scheme(HttpScheme.HTTP.toString())
                                .host(REQUEST_HOST)
                                .path(REQUEST_PATH)
                                .build(KEY, TYPE, SERVICE, START_INDEX, END_INDEX, subwayNm)
                                .toString()
                )
                .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
                .retrieve()
                .bodyToMono(RealtimeSubwayResponseDto.class)
                .block();
    }
}
  • 필요한 값들을 모두 미리 선언(String 직접 사용하는 하드코딩 자제하기 위해)
  • 생성한 WebClient 객체에 get 방식으로 호출하라는 규약으로 인해 .get()사용
  • webclient는 UriComponentsBuilder를 통해 URI를 빌딩할 수 있음
  • 생성된 URI 요청 주소는 http://swopenAPI.seoul.go.kr/api/subway/{KEY}/{TYPE}/{SERVICE}/{START_INDEX}/{END_INDEX}/{subwayNm} 형태인데 각각의 값들을 .build()안에 주입해주어 PATH 완성
  • .header()에 JSON형태로 받기위해 세팅해주고 값을 RealtimeSubwayResponseDto.class로 반환받음
  • 해당 예제에서는 .block()을 사용하여 동기식으로 진행했으나 추후 공부하여 비동기식으로 바꿔 WebClient의 장점을 살리려 refactoring할 것임

DTO

@Data
public class RealtimeSubwayResponseDto {
    private final RealtimeSubwayErrorResponseDto errorMessage;
    private final List<RealtimePositionResponseDto> realtimePositionList;
}
@Data
public class RealtimeSubwayErrorResponseDto {
    private final String status;
    private final String code;
    private final String message;
    private final String link;
    private final String developerMessage;
    private final Integer total;
}
@Data
public class RealtimePositionResponseDto {
    // 공통
    private final int list_total_count;
    private final String RESULT_CODE;
    private final String RESULT_MESSAGE;

    // 열차 정보
    private final int subwayId;
    private final String subwayNm;
    private final int statnId;
    private final String statnNm;
    private final String trainNo;
    private final String lastRecptnDt;
    private final String recptnDt;
    private final int updnLine;
    private final int statnTid;
    private final String statnTnm;
    private final int trainSttus;
    private final int directAt;
    private final int lstcarAt;

}

얻어갈 점

  • WebClient를 이용해 호출할 때 처음에 KEY와 같은 값을 쿼리파라미터로 넘기려해서 요청 실패함
  • 하지만 PATH에 걸려있기 때문에 .build()에 넣어주는 게 정답
  • 제대로 요청을 완료했지만 DTO에 값이 담기지 않는 이슈 발생
  • 원인을 알기 위해 반환값을 DTO가 아닌 String으로 변환하여 반환값 확인
  • 초기에는 한 DTO에 모든 변수를 선언하여 받으려 했으나 반환값을 확인하니 한 객체에 또 다른 두 개의 레퍼런스가 있는 구조였음
  • DTO 구조 수정 후 정상작동 확인
  • 반환값이 제대로 나오지 않아 궁금할 때는 String으로 찍어보는 것도 좋을 것 같음

0개의 댓글