API 호출을 했는데 가만히 생각해보니 중요한 key값을 전부 노출하고, 로직과 상관없는 변수들이 계속해서 발생하는 상황이 많이 불편했다. 따라서 properties 텍스트 파일을 통해 이러한 변수들을 따로 관리하게 되었다.
검색을 하다보니 좋은걸 발견했다.
바로 PropertySource라는 어노테이션인데, Property를 환경 변수로 편리하고 쉽게 집어넣어주는 기능을 한다. 이 문서를 읽기 전에는 상당히 골치 아팠다.
레퍼런스 : https://it-pig.tistory.com/24

들어가는 데이터 타입/함수 등을 정리해 보자.
아.. 물론 코드가 어떻게 돌아가는지 정확하게 파악한 후 코드를 읽는것이 매우 중요하긴 하다.
하지만 이러한 코드의 흐름을 다 외우고 적용하는것은 내 실력에 한계가 있을 뿐더러,
저러한 함수들을 모두 외우고 있는건 상당히 골치아프다고 생각했다.
하지만, PropertySource를 활용하게 되면
이거 하나만 class에 어노테이션 달아주면 끝나는 것이다.
Service
@Service
public class Public_service {
private final WebClient webClient;
private final Environment env;
@Autowired
public Public_service(WebClient webClient, Environment env) {
this.webClient = webClient;
this.env = env;
}
public Mono<String> getProductPriceInfo() {
String baseUrl = env.getProperty("baseUrl");
String publicPath = env.getProperty("publicPath");
String queryParams = env.getProperty("queryParams");
String fullUrl = baseUrl + publicPath + "?" + queryParams;
return webClient.get()
.uri(URI.create(fullUrl))
.retrieve()
.bodyToMono(String.class)
.doOnError(e -> logger.error("Error during API call", e));
}
}
Config
@Configuration
@PropertySource("classpath:api.properties")
public class Public_config {
@Bean
public WebClient webClient() {
return WebClient.builder()
.filter(logRequest())
.build();
}
}
코드의 흐름을 살펴보자.
우리는 저 어노테이션 하나로 원하는 데이터를 환경변수로 등록해 어디에서든 사용할 수 있게 되는 것이다!
어노테이션을 지워보았다. 그럼 다음과 같은 현상이 일어난다.


그래서 데이터를 불러오면 null이 뜬다.(환경변수에는 baseUrl, publicPath 같은 변수가 없기 때문)
그럼 환경변수는 불러와 질까?

gpt에게 환경변수를 물어본 다음

변수를 호출하게 되면

잘 나오는 모습을 볼 수 있다.
그리고 모든 환경변수가 나오는건 아니고

안나오는 변수도 있다.
이렇게 변수 불러오기는 끝났다!
이제 데이터를 받아오기 위해서 API를 호출해야 한다.
받아오기 앞서, API uri를 정리해 보자.
데이터 정리에 앞서, 필수값은 (ES), 선택값은 (CH)로 나타내겠다.
1. 한국전력 가구평균 전력 사용량
2. 한국소비자원 상품정보 조회
3. 한국소비자원 판매점 정보 조회
4. 한국소비자원 생필품 가격정보 조회
5. 한국소비자원 기준 데이터 조회
classCode는 다음과 같다.
6. 건강보험심사평가원 병원정보서비스
7. 기상청 단기예보조회
8. 기상청 중기육상예보조회
종류별로 정리하면
BaseURL
Path
Parameter
가 되겠다.
하지만 위의 데이터가 모두 들어가는건 아닌것이
Parameter는 변수이기 때문에 다른곳에서 값을 받아오거나 직접 입력할 것이다.

그리고 이를 properties에 정리하였다.
위에서 말했던 가변값 중에는 날짜가 있다.
필요한 데이터는 다음과 같다.
날짜를 구하는 방식은 다음과 같다.
LocalDate now = LocalDate.now();
LocalDate LastFriday = now.with(TemporalAdjusters.previous((DayOfWeek.FRIDAY)));
DateTimeFormatter DayToYYYY = DateTimeFormatter.ofPattern("yyyy");
DateTimeFormatter DayToMM = DateTimeFormatter.ofPattern("MM");
DateTimeFormatter DayToYYYYMMDD = DateTimeFormatter.ofPattern("yyyyMMdd");
String YYYY = now.format(DayToYYYY); //2024
String MM = now.format(DayToMM); //08
String YYYYMMDD = now.format(DayToYYYYMMDD);// 20240806
주의!

day와 time을 나눠서 써야 한다.
LocalDate now_day = LocalDate.now();
LocalTime now_time = LocalTime.now();
위와 같이 날짜와 관련된 것은 LocalDate, 시간과 관련된 것은 LocalTime을 사용해야 한다.

만약에 시간 함수를 쓰다가 위와 같은 의존성 문제가 발생한다면, Date와 Time을 잘못 썼는지 확인해 보자.
코딩을 하다보니 묘하게 열받는 부분이 있었다.
public Mono<String> getProductPriceInfo() {
String baseUrl = env.getProperty("baseUrl");
String publicPath = env.getProperty("publicPath");
String queryParams = env.getProperty("queryParams");
String fullUrl = baseUrl + publicPath + "?" + queryParams;
return webClient.get()
.uri(URI.create(fullUrl))
.retrieve()
.bodyToMono(String.class)
.doOnError(e -> logger.error("Error during API call", e));
}
바로 api 호출 코드인데, 코드를 작성하다보니 return 값이 계속 중복이 되는 것이었다.
그래서 바꿨다.
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() {
String URL = env.getProperty("KCA_URL");
String PATH = env.getProperty("KCA_ProdPrice_Path");
String queryParams = "goodInspectDay=" + LF + "&entpId=100&ServiceKey=" + env.getProperty("DATA_Key");
return makeApiCall(URL, PATH, queryParams);
}
return 부분을 함수화 시켜서 빼내고, 호출하는 식으로 바꾸니 중복코드가 없어졌다.
하지만 묘하게 열받는 부분이 또 있었으니,
String URL이라고 선언해놓고 return 값에 한번더 집어넣는 것이었다.
그래서 한번더 바꿨다.
public Mono<String> getProductPriceInfo() {
return makeApiCall(
env.getProperty("KCA_URL"),
env.getProperty("KCA_ProdPrice_Path"),
"goodInspectDay=" + LF + "&entpId=100&ServiceKey=" + env.getProperty("DATA_Key")
);
}
편안하다.
12줄짜리 코드가 7줄 + return(8줄) 코드로 리팩터링 되었다.
총 줄수는 늘어난거 아닌가? 싶지만
또한 날짜 관련해서도 리팩토링을 좀 해봤는데,
//이전양식
String FridayFormat = DateTimeFormatter.ofPattern("yyyyMMdd")
String LF = LastFriday.format(FridayFormat);
//바꾼 양식
String LF = LastFriday.format(DateTimeFormatter.ofPattern("yyyyMMdd"));
선언 -> 호출 형식에서 하나로 묶어버렸다.(갑작스럽게 나왔지만 api 호출할때 필요해서 만들었다.)
java 코딩이 익숙하지 않아서 절차를 생각하며 코딩하다보니 코드가 엄청 길어지는 경향이 있었는데,
리팩토링 하면서 코드를 줄이니까 가독성이 훨씬 좋아지는걸 느꼈다.
## Location Code ##
## 서울11,부산21,대구22,인천23,광주24,대전25,울산26,경기도31,강원도32,충북33,충남34,전북35,전남36,경북37,경남38,제주39,세종41
KEPCO_Code=11,21,22,23,24,25,26,31,32,33,34,35,36,37,38,39,41
@Value("#{'${KEPCO_Code}'.split(',')}")
private List<String> kepcoCode;
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")
));
}
또한 Value, Flux를 통해 한전 데이터를 받아오는 코드를 만들었다.
지역 코드가 많기에 이를 list로 받아올 필요가 있었는데, @Value 어노테이션을 사용하면 변수를 List로 만들 수 있다.
이후 Flux를 활용하여 List 요소를 대입하여 실행시키게 되었다.
Python의 for문과 같다고 보면 된다.
하지만 슬픈게.. metroCd가 실제 요청과 맞지 않아 문의를 넣은 상태이다. 내일 다시 연락을 준다고 한다.
이후 문의를 받았다.

여기서 시도 코드가 선택이 되어야 하는데 필수가 된거는 조취를 해주신다고 했고,

이건 이러한 API를 처음 써봐서 생긴 실수였는데,
metroCd 가 아닌 LgIDngMetroCd를 써야 한다고 했다.
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")
));
}

이후 Flux를 통해서 여러 요청을 받아오는것도 잘 처리가 된 모습이다.
이로써 API를 통해 데이터 받아오는건 모두 해결했다!