본문 바로가기

개발공부/개발일지

[WebClient] 공공데이터 openAPI 호출 WebClient 사용해서 리팩토링 하기

메인 프로젝트에서 견주 인증 기능 구현을 위해 동물 등록 번호 조회 공공데이터 openAPI를 사용했었다.  프로젝트 기간 당시에는 시간이 빠듯해서 일단 작동하는 공공데이터 포털에서 제공해주는 샘플 코드를 활용했다. 샘플 코드는 Java 1.8 기준이었고, 코드 내용도 전체적으로 옛날 방식으로 길었다.

멘토님 조언도 있었고, 이 부분은 꼭 리팩토링을 해야지 생각하고 있다가 오늘 드디어 WebClient를 사용해서 훨씬 간결하게 수정했다. 

WebClient 사용은 처음해보기 때문에 깊게 이해하지는 못했지만 openAPI get 요청으로 호출하는 부분 위주로 자료를 찾아보면서 적용했다.

물론 성공하기까지 몇 번의 에러를 만났지만 결과적으로는 학습에 더 도움이 된 것 같다.

 

 

먼저 WebClient 사용을 위해서 build.gradle에 의존성 추가를 해줬다.

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

 

 

일단 결과물부터 보는 게 확실하니까 비교를 위해 WebClient 사용 전과 후의 코드를 첨부했다. 

비즈니스 로직이라서 Service 클래스에서 처리하도록 했다.

 (servicekey나 baseURL은 환경변수로 빼두고 사용했다.)

 

이전 코드

public Dog registerRegNo(DogValidationPostDto dogValidationPostDto) throws IOException, ParseException {
	StringBuilder urlBuilder = new StringBuilder(dataUrl);
        urlBuilder.append("?" + URLEncoder.encode("serviceKey", "UTF-8") + key);
        urlBuilder.append("&" + URLEncoder.encode("dog_reg_no", "UTF-8") + "=" + URLEncoder.encode(dogValidationPostDto.getDog_reg_no(), "UTF-8"));
        urlBuilder.append("&" + URLEncoder.encode("owner_nm", "UTF-8") + "=" + URLEncoder.encode(dogValidationPostDto.getOwner_nm(), "UTF-8"));
        urlBuilder.append("&" + URLEncoder.encode("_type", "UTF-8") + "=" + URLEncoder.encode("json", "UTF-8"));
        URL url = new URL(urlBuilder.toString());
        HttpURLConnection conn = (HttpURLConnection) url.openConnection();
        conn.setRequestMethod("GET");
        conn.setRequestProperty("Content-type", "application/json");

        System.out.println("Response code: " + conn.getResponseCode());

        BufferedReader rd;
        if (conn.getResponseCode() >= 200 && conn.getResponseCode() <= 300) {
            rd = new BufferedReader(new InputStreamReader(conn.getInputStream()));
        } else {
            rd = new BufferedReader(new InputStreamReader(conn.getErrorStream()));
        }
        StringBuilder sb = new StringBuilder();
        String line;
        while ((line = rd.readLine()) != null) {
            sb.append(line);
        }
        rd.close();
        conn.disconnect();
        String jsonData = sb.toString();

        JSONObject objData = (JSONObject) new JSONParser().parse(jsonData);
        JSONObject response = (JSONObject) objData.get("response");
        JSONObject body = (JSONObject) response.get("body");

        if (!body.isEmpty()) {
            Long memberId = (Long) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
            verifyDogRegNo(dogValidationPostDto.getDog_reg_no());
            return dogRepository.save(Dog.builder()
                    .dogRegNo(dogValidationPostDto.getDog_reg_no())
                    .member(memberService.verifyMember(memberId))
                    .build());
        }
        if (body.isEmpty()) {
            throw new BusinessLogicException(ExceptionCode.DOG_INFO_NOT_VALID);
        }
        return null;

한 눈에 보기에도 길고 if문도 불필요하게 사용되었다. 이 때는 이게 최선이라고 생각해서 썼는데 다시 보니까 너무 민망한 로직이다..

 

 

그리고 이번엔 WebClient를 사용해서 수정한 코드이다.

 

WebClient을 사용한 리팩토링 코드

    public Dog registerRegNo(DogValidationPostDto dogValidationPostDto) throws UnsupportedEncodingException {

        String regNo = dogValidationPostDto.getDog_reg_no();
        String owner = dogValidationPostDto.getOwner_nm();

        String decodeServiceKey = URLDecoder.decode(key, "UTF-8");

        String result = WebClient.create(dataUrl)
                .get()
                .uri("?serviceKey=" + decodeServiceKey + "&dog_reg_no=" + regNo +  "&owner_nm=" + owner +"&_type=json")
                .retrieve()
                .bodyToMono(String.class)
                .block();

        JSONObject jsonObject = new JSONObject(result);
        JSONObject response = jsonObject.getJSONObject("response");
        JSONObject body = response.getJSONObject("body");
        Long memberId = (Long) SecurityContextHolder.getContext().getAuthentication().getPrincipal();

        if (!body.isEmpty()) {
            verifyDogRegNo(dogValidationPostDto.getDog_reg_no());
            return dogRepository.save(Dog.builder()
                    .dogRegNo(dogValidationPostDto.getDog_reg_no())
                    .member(memberService.verifyMember(memberId))
                    .build());
        }
        else throw new BusinessLogicException(ExceptionCode.DOG_INFO_NOT_VALID);
    }

 

눈에 띄는 변화는 일단 openAPI를 호출하는 부분이 훨씬 간단해졌다. 그리고 if문도 불필요한 부분을 수정했다. if문에서 body값의 유무로 판단하는 이유는 올바른 견주 인증 요청이 들어왔을 경우, 공공데이터 응답 데이터에 body가 포함되어 있지만 잘못된 등록번호로 요청을 할 경우, body값이 없는 상태로 응답이 온다. 그 차이를 이용해서 올바른 동물등록번호인지 구별하도록 했다. 

 

그리고 제대로 된 요청일 때 등록번호를 저장하는 이유는 동물등록번호의 중복 사용을 방지하기 위해서이다. 악의적인 유저가 도용한 동물등록번호를 사용해서 견주 인증을 시도할 때 중복을 방지해놓지 않으면 도용같은 경우를 막을 수 없기때문에 필요했다. 테이블 쪽에도 unique 제약 조건을 걸어두었지만 그래도 이중으로 해놨다. 

 

 

맞닥드렸던 오류

SERVICE KEY IS NOT REGISTERED ERROR

--> 서비스 키를 그냥 사용하면 인코딩되어있기 때문에 오류가 발생했던 거였다. 그래서 decode 코드를 넣어주니 해결됐다.

 

json object 파싱은 Gson이나 ObjectMapper를 사용하려다가 그 안의 내용을 다 쓰는 게 아니라 덩어리 묶음으로 응답이 있는지만 보는거라서 크게 손보지는 않았다. 그래도 다른 부분 수정하면서 한 번 수정해볼 예정이다.