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();
}
}
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으로 찍어보는 것도 좋을 것 같음