코드를 보다가
private Mono<String> makeApiCall(String URL, String path, String queryParams) {
String fullUrl = URL + path + queryParams;
return webClient.get()
.uri(URI.create(fullUrl))
.retrieve()
.bodyToMono(String.class)
.doOnError(e -> logger.error("Error during API call", e));
}
public Mono<String> getProductPriceInfo() {
return makeApiCall(
env.getProperty("KCA_URL"),
env.getProperty("KCA_ProdPrice_Path"),
"goodInspectDay=" + LF + "&entpId=100&ServiceKey=" + env.getProperty("DATA_Key")
);
}
public Mono<String> getProdInfo() {
return makeApiCall(
env.getProperty("KCA_URL"),
env.getProperty("KCA_ProdInfo_Path"),
"ServiceKey=" + env.getProperty("DATA_Key")
);
}
public Mono<String> getStoreInfo() {
return makeApiCall(
env.getProperty("KCA_URL"),
env.getProperty("KCA_StoreInfo_Path"),
"ServiceKey=" + env.getProperty("DATA_Key"));
}
public Flux<String> getKEPCO() {
return Flux.fromIterable(kepcoCode)
.flatMap(code -> makeApiCall(
env.getProperty("KEPCO_URL"),
env.getProperty("KEPCO_Path"),
"year=" + LY + "&month=" + MM + "&metroCd=" + code + "&apiKey=" + env.getProperty("KEPCO_Key")
));
}
public Mono<String> getKEPCO_2() {
return makeApiCall(
env.getProperty("KEPCO_URL"),
env.getProperty("KEPCO_Path"),
"year=" + LY + "&month=" + MM + "&apiKey=" + env.getProperty("KEPCO_Key"));
}
public Mono<String> getHIRAInfo() {
return makeApiCall(
env.getProperty("HIRA_URL"),
env.getProperty("HIRA_HospBase_Path"),
"ServiceKey=" + env.getProperty("DATA_Key"));
}
public Mono<String> getShortKMA() {
return makeApiCall(
env.getProperty("KMA_URL"),
env.getProperty("KMA_Short_Path"),
"ServiceKey=" + env.getProperty("DATA_Key"));
}
public Mono<String> getMidKMA() {
return makeApiCall(
env.getProperty("KMA_URL"),
env.getProperty("KMA_Mid_Path"),
"ServiceKey=" + env.getProperty("DATA_Key"));
}
}
API 호출 Service가 계속 반복되는걸 봤다.
곰곰히 생각해보니 URL, Path, Key의 반복이고 파라미터값만 좀 바꾸면 되니
코드를 압축시킬 수는 없을까 싶어서 리팩토링을 해봤다.
private Mono<String> makeApiCall(String URL, String path, String queryParams) {
String fullUrl = URL + path + queryParams;
return webClient.get()
.uri(URI.create(fullUrl))
.retrieve()
.bodyToMono(String.class)
.doOnError(e -> logger.error("Error during API call", e));
}
public Flux<String> getAllApiResponses() {
return Flux.range(0, urlList.size())
.flatMap(i -> {
String url = urlList.get(i);
String path = pathList.get(i);
String key = keyList.get(i);
String queryParams = "ServiceKey=" + key;
// 필요한 경우 각 API에 맞는 추가적인 쿼리 파라미터를 설정
if (url.contains("KEPCO")) {
queryParams = "year=" + LY + "&month=" + MM + "&metroCd=" + kepcoCode.get(0) + "&apiKey=" + key;
} else if (url.contains("KCA")) {
queryParams += "&goodInspectDay=" + LF + "&entpId=100";
}
return makeApiCall(url, path, queryParams);
});
}
}
확실히 장난아니게 줄어들었다.
기존에는 96줄짜리 코드가 60줄로 줄어들었다.
이는 반복코드를 제외한 변수 설정까지 다 합한 것이기 때문에,
실질적으로는 56줄짜리 코드가 17줄로 변한 것이다.
여기까지는 좋다. 정말 좋은데 문제가 발생했다.

Properties 파일의 변수가 감당이 안된다..
이렇게 되면 문제가 발생하는게

기존에 쓰던 Properties 형식이다. 용도에 따라 분류해놓았다.
Location Code는 인코딩 설정이 잘못되서 한글이 다 깨졌는데, 설정을 바꾸고 다시 적어야 한다.
그래서 나에게는 2가지 조건이 생겨버렸다.
그럼 내가 가진 코드중에서 반복되는 구문이 뭐가있을까?
String fullUrl = URL + path + queryParams;
이 코드이다.
API 호출과 변수 설정은 고정된 값이지만
API URL은 호출 개수가 늘어나는 만큼 계속해서 증가한다.
따라서, URL 호출 코드를 한줄로 바꿀 수 있다면 획기적으로 코드를 간소화 시킬수 있다고 생각했고,
레퍼런스 : https://d2.naver.com/helloworld/2771091

Mono.zip라는 코드를 알게 되었다.
위 코드는 여러가지의 Mono들을 하나로 묶어주는 역할을 한다!
private Mono<String> makeApiCall(String baseUrlKey, String pathKey, String... queryParams) {
String baseUrl = env.getProperty(baseUrlKey);
String path = env.getProperty(pathKey);
String fullUrl = baseUrl + path + String.join("&", queryParams);
return webClient.get()
.uri(URI.create(fullUrl))
.retrieve()
.bodyToMono(String.class)
.doOnError(e -> logger.error("Error during API call", e));
}
public Mono<Tuple6<String, String, String, String, String, String>> All_Api_URI() {
return Mono.zip(
makeApiCall("KCA_URL", "KCA_ProdPrice_Path", "goodInspectDay=" + LF, "entpId=100", "ServiceKey=" + env.getProperty("DATA_Key")),
makeApiCall("KCA_URL", "KCA_ProdInfo_Path", "ServiceKey=" + env.getProperty("DATA_Key")),
makeApiCall("KCA_URL", "KCA_StoreInfo_Path", "ServiceKey=" + env.getProperty("DATA_Key")),
makeApiCall("HIRA_URL", "HIRA_HospBase_Path", "ServiceKey=" + env.getProperty("DATA_Key")),
makeApiCall("KMA_URL", "KMA_Short_Path", "ServiceKey=" + env.getProperty("DATA_Key")),
makeApiCall("KMA_URL", "KMA_Mid_Path", "ServiceKey=" + env.getProperty("DATA_Key"))
);
}
데이터 불러오는 코드가 단 10줄로 변해버린 기적이 발생했다!
57줄에서 10줄로 줄어든거면 20프로 이하로 압축이 되었다는 것이다.

지금 내가 적은 코드는 문제점이 하나 있는데,
그것은 Mono.zip의 구성요소 중 하나라도 오류가 나면 에러가 발생한다는 것이다.
그래서 왜이런지 Postman으로 확인을 해봤는데


아.. 데이터의 업로드 주기가 일정하지 않는것이다.
분명히 저번에 코드를 적을때는 전주 데이터가 잘 들어왔었는데,
이번에는 데이터 업로드가 느린지 6일이 지나도 데이터가 업데이트 되지 않았다.
따라서, 2주전 데이터를 가져오기로 했다.
또 에러가 뜬다.

뭔가 싶어서 코드를 봤더니
"message": "200 OK from GET http://openapi.price.go.kr/openApiImpl/ProductPriceInfoService/getStoreInfoSvc.do",
200OK를 받았고,
*__checkpoint ⇢ Body from GET http://openapi.price.go.kr/openApiImpl/Produ~~
각각의 API 통신도 재대로 이루어진걸 확인할 수 있다.
어이가없어서 코드를 자세히 확인하니까
Exceeded limit on max bytes to buffer : 262144
이상한 오류가 떴다.
버퍼가 터졌다고 한다.
레퍼런스 : https://garyj.tistory.com/22
데이터의 용량이 너무 커서 오류가 났다고 한다.
@Autowired
public Public_service(WebClient.Builder webClientBuilder, Environment env) {
this.webClient = webClientBuilder
.exchangeStrategies(ExchangeStrategies.builder()
.codecs(configurer -> configurer.defaultCodecs().maxInMemorySize(10 * 1024 * 1024)) // 10MB로 설정
.build())
.build();
this.env = env;
}
WebClient 버퍼 설정을 해줬고

데이터가 잘 들어오는걸 확인할 수 있다.
사이즈 크기를 보고 놀랐는데, 뭔 text 파일 크기가 650KB나 된다
버퍼가 터질 만했다..
그리고 눈치가 좀 빠르신분들은 알아차리셨겠지만
데이터가 4개밖에 들어오지 않는다.
사실, API 요청인자 때문에 한전 전력데이터 개방 포털시스템쪽에 문의를 넣었다.

metroCd의 세부내용을 보면 ※미선택시 모든 시도 응답이라고 되어있는데,

그런거없고 얄짤없이 400이 뜬다.
그래서 이 데이터는
KEPCO_Code=11,26,27,28,29,30,31,36,41,43,44,45,46,47,48,50,51
Code를 활용하여 리스트 형식으로 받아올 것이다.
근데 막상 코드를 짜보니
public Mono<Tuple4<String, String, String, String>> KEPCO_Api_URI() {
return Mono.zip(
makeApiCall("KEPCO_URL", "KEPCO_Path", "year=" + LY, "month=" + MM, "metroCd=" + kepcoCode.get(0), "apiKey=" + env.getProperty("KEPCO_Key")),
makeApiCall("KEPCO_URL", "KEPCO_Path", "year=" + LY, "month=" + MM, "metroCd=" + kepcoCode.get(1), "apiKey=" + env.getProperty("KEPCO_Key")),
makeApiCall("KEPCO_URL", "KEPCO_Path", "year=" + LY, "month=" + MM, "metroCd=" + kepcoCode.get(2), "apiKey=" + env.getProperty("KEPCO_Key")),
makeApiCall("KEPCO_URL", "KEPCO_Path", "year=" + LY, "month=" + MM, "metroCd=" + kepcoCode.get(3), "apiKey=" + env.getProperty("KEPCO_Key")),
makeApiCall("KEPCO_URL", "KEPCO_Path", "year=" + LY, "month=" + MM, "metroCd=" + kepcoCode.get(4), "apiKey=" + env.getProperty("KEPCO_Key")),
makeApiCall("KEPCO_URL", "KEPCO_Path", "year=" + LY, "month=" + MM, "metroCd=" + kepcoCode.get(5), "apiKey=" + env.getProperty("KEPCO_Key")),
makeApiCall("KEPCO_URL", "KEPCO_Path", "year=" + LY, "month=" + MM, "metroCd=" + kepcoCode.get(6), "apiKey=" + env.getProperty("KEPCO_Key")),
makeApiCall("KEPCO_URL", "KEPCO_Path", "year=" + LY, "month=" + MM, "metroCd=" + kepcoCode.get(7), "apiKey=" + env.getProperty("KEPCO_Key")),
makeApiCall("KEPCO_URL", "KEPCO_Path", "year=" + LY, "month=" + MM, "metroCd=" + kepcoCode.get(8), "apiKey=" + env.getProperty("KEPCO_Key")),
makeApiCall("KEPCO_URL", "KEPCO_Path", "year=" + LY, "month=" + MM, "metroCd=" + kepcoCode.get(9), "apiKey=" + env.getProperty("KEPCO_Key")),
makeApiCall("KEPCO_URL", "KEPCO_Path", "year=" + LY, "month=" + MM, "metroCd=" + kepcoCode.get(10), "apiKey=" + env.getProperty("KEPCO_Key")),
makeApiCall("KEPCO_URL", "KEPCO_Path", "year=" + LY, "month=" + MM, "metroCd=" + kepcoCode.get(11), "apiKey=" + env.getProperty("KEPCO_Key")),
makeApiCall("KEPCO_URL", "KEPCO_Path", "year=" + LY, "month=" + MM, "metroCd=" + kepcoCode.get(12), "apiKey=" + env.getProperty("KEPCO_Key")),
makeApiCall("KEPCO_URL", "KEPCO_Path", "year=" + LY, "month=" + MM, "metroCd=" + kepcoCode.get(13), "apiKey=" + env.getProperty("KEPCO_Key")),
makeApiCall("KEPCO_URL", "KEPCO_Path", "year=" + LY, "month=" + MM, "metroCd=" + kepcoCode.get(14), "apiKey=" + env.getProperty("KEPCO_Key")),
makeApiCall("KEPCO_URL", "KEPCO_Path", "year=" + LY, "month=" + MM, "metroCd=" + kepcoCode.get(15), "apiKey=" + env.getProperty("KEPCO_Key")),
makeApiCall("KEPCO_URL", "KEPCO_Path", "year=" + LY, "month=" + MM, "metroCd=" + kepcoCode.get(16), "apiKey=" + env.getProperty("KEPCO_Key")),
);
}
이런.. 모양이 나와버렸다.
도저히 볼 수가 없어서 Flux로 바꿨다.
public Flux<String> KEPCO_Api_URI() {
return Flux.fromIterable(kepcoCode)
.flatMap(code -> makeApiCall("KEPCO_URL", "KEPCO_Path", "year=" + LY, "month=" + MM, "metroCd=" + code, "apiKey=" + env.getProperty("KEPCO_Key")));
}
가로로 길어졌지만, 관심이 있으면 한번 읽고 파악할 수 있고,
관심이 없다면 한전 데이터 호출코드구나~ 하고 넘어갈 것이다.
그런데 또 터졌다 ㅋㅋ
org.springframework.web.reactive.function.client.WebClientRequestException: connection timed out after 30000 ms: bigdata.kepco.co.kr/
요약을 하자면, 호출을 하고 30초가 넘었는데도 응답이 안된다는 것이다.
뭔가 싶어서 properties를 줄이면서 실험을 했는데


Path 뒤에 "?"가 2개 붙은거였다
근데 더 골때리는거는
KEPCO 제외 KCA / HIRA 쪽은 호출할때 ??가 붙어도 오류가 안난다.

아니 왜 여기는 되고 저기는 안되는건지 도저히 이해가 안됐지만 어쨌든 고쳤는데

또터졌다 ㅋㅋ
한번더 자세하게 살펴보니
Making request to URL: https://bigdata.kepco.co.kr/openapi/v1/powerUsage/houseAve.do??year=2023&month=08&metroCd=11&apiKey=0m82zd7ldQA89hapC6WOZocz08R1ut5fg7UJ68rb
Making request to URL: https://bigdata.kepco.co.kr/openapi/v1/powerUsage/houseAve.do??year=2023&month=08&metroCd=26&apiKey=0m82zd7ldQA89hapC6WOZocz08R1ut5fg7UJ68rb
.....
아니 왜 https로 보내고있지? 하고 Properties를 확인하니
KEPCO_URL=https://bigdata.kepco.co.kr
바로 바꾸고 API 응답을 확인했다.
그런데 이번에는 데이터가 나오질 않는다.

뭔가 싶어서 찾아보니
레퍼런스 : https://www.reddit.com/r/SpringBoot/comments/wlxz8a/flux_not_returning_any_response/
sse 설정을 하라는 것이었고, 이를 반영해서
@GetMapping(value = "/KEPCO", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<String> getKEPCO_Data() {
return publicService.KEPCO_Api_URI();
}
코드를 설정하니

데이터가 신기한 방식으로 들어오는걸 확인할 수 있었다.
전체코드는 다음과 같다.
@Autowired
public Public_service(WebClient.Builder webClientBuilder, Environment env) {
this.webClient = webClientBuilder
.exchangeStrategies(ExchangeStrategies.builder()
.codecs(configurer -> configurer.defaultCodecs().maxInMemorySize(10 * 1024 * 1024)) // 10MB로 설정
.build())
.build();
this.env = env;
}
private Mono<String> makeApiCall(String baseUrlKey, String pathKey, String... queryParams) {
String baseUrl = env.getProperty(baseUrlKey);
String path = env.getProperty(pathKey);
String fullUrl = baseUrl + path + "?" + String.join("&", queryParams);
return webClient.get()
.uri(URI.create(fullUrl))
.retrieve()
.bodyToMono(String.class)
.doOnError(e -> logger.error("Error during API call", e));
}
public Mono<Tuple4<String, String, String, String>> KCA_HIRA_Api_URI() {
return Mono.zip(
makeApiCall("KCA_URL", "KCA_ProdPrice_Path", "goodInspectDay=" + LF, "entpId=100", "ServiceKey=" + env.getProperty("DATA_Key")),
makeApiCall("KCA_URL", "KCA_ProdInfo_Path", "ServiceKey=" + env.getProperty("DATA_Key")),
makeApiCall("KCA_URL", "KCA_StoreInfo_Path", "ServiceKey=" + env.getProperty("DATA_Key")),
makeApiCall("HIRA_URL", "HIRA_HospBase_Path", "ServiceKey=" + env.getProperty("DATA_Key"))
);
}
public Flux<String> KEPCO_Api_URI() {
return Flux.fromIterable(kepcoCode)
.concatMap(code -> {
logger.info("Making API call for metroCd: {}", code);
return makeApiCall("KEPCO_URL", "KEPCO_Path", "year=" + LY, "month=" + MM, "metroCd=" + code, "apiKey=" + env.getProperty("KEPCO_Key"))
.doOnNext(response -> logger.info("Received response: {}", response))
.doOnError(error -> logger.error("Error during KEPCO API call", error));
});
}
이전의 복잡했던 코드에 비해서
훨씬 깔끔하게 짜여진것 같다.