[Feat] 등산 기록 상세 조회에 등산 당시 기온 정보 추가#181
Conversation
|
Important Review skippedAuto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Repository UI Review profile: CHILL Plan: Pro Plus Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Summary of ChangesHello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed! 본 PR은 등산 기록 상세 조회 시 등산 당시의 기온 정보를 제공하기 위해 기상청 API를 연동하는 기능을 포함하고 있습니다. 트래킹 세션이 완료되는 시점에 자동으로 해당 위치의 기온을 조회하여 저장하며, 이를 위해 필요한 데이터베이스 스키마 업데이트와 외부 API 통신을 위한 설정 및 서비스 로직이 추가되었습니다. Highlights
New Features🧠 You can now enable Memory (public preview) to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console. Using Gemini Code AssistThe full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips. Invoking Gemini You can request assistance from Gemini at any point by creating a comment using either
Customization To customize the Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a Limitations & Feedback Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counterproductive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here. Footnotes
|
There was a problem hiding this comment.
Code Review
This pull request integrates an external weather API to fetch and record the temperature when completing a hiking session. It introduces a WebClient configuration, a coordinate converter, a WeatherService, and updates the HikingRecord entity and database schema. Key feedback includes: avoiding external API calls within database transactions to prevent connection pool exhaustion, specifying the KST timezone for time calculations, preventing double-encoding of the API service key in WebClient, and handling inconsistent API response formats defensively to avoid ClassCastExceptions.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
| Mountain mountain = session.getMountain(); | ||
| if (mountain.getLatitude() != null && mountain.getLongitude() != null) { | ||
| weatherService.getTemperature(mountain.getLatitude(), mountain.getLongitude()) | ||
| .ifPresent(record::updateTemperature); | ||
| } |
There was a problem hiding this comment.
[P1] DB 트랜잭션 내 외부 API 호출로 인한 커넥션 점유 위험
complete 메서드는 @Transactional 어노테이션이 적용되어 있어 데이터베이스 트랜잭션 내에서 실행됩니다. 이 안에서 weatherService.getTemperature를 호출하여 외부 기상청 API와 동기식(blocking)으로 통신하고 있습니다.
문제점
- 외부 API 호출에 최대 3초의 타임아웃이 설정되어 있지만, 이 시간 동안 데이터베이스 커넥션을 계속 점유하게 됩니다.
- 트래픽이 몰리거나 기상청 API의 응답이 지연될 경우, DB 커넥션 풀 고갈(Connection Pool Exhaustion)로 이어져 전체 시스템 장애를 유발할 수 있습니다.
해결 방안
기온 정보는 등산 기록의 핵심 비즈니스 로직이 아니므로, 메인 트랜잭션 외부에서 처리하는 것이 안전합니다.
- 대안 1: 세션 완료 트랜잭션이 성공적으로 커밋된 후, 비동기 이벤트 리스너(
@Async및@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT))를 통해 기온을 조회하고HikingRecord를 업데이트합니다. - 대안 2:
complete메서드 진입 시점에 트랜잭션 없이 기온을 먼저 조회한 후, 조회된 기온을 트랜잭션이 적용된 내부 메서드에 전달하여 저장합니다.
References
- 트랜잭션 경계 설정 및 외부 사이드 이펙트 호출 시 메인 비즈니스 로직에 미치는 영향(롤백 및 커넥션 점유 등)을 신중하게 검토해야 합니다. (link)
| public Optional<Double> getTemperature(double latitude, double longitude) { | ||
| try { | ||
| GridConverter.Grid grid = GridConverter.toGrid(latitude, longitude); | ||
| LocalDateTime now = adjustBaseTime(LocalDateTime.now()); |
There was a problem hiding this comment.
[P1] 서버 시간대(Timezone) 차이로 인한 기상청 API 조회 실패 위험
LocalDateTime.now()를 사용하여 현재 시간을 가져오고 있습니다.
문제점
- 일반적으로 클라우드 환경(AWS EC2, Docker 등)의 서버 시간대는 UTC(세계 협정시)로 설정되어 있는 경우가 많습니다.
- 이 경우
LocalDateTime.now()는 한국 시간(KST)보다 9시간 느린 시간을 반환하므로, 기상청 API에 잘못된base_date와base_time을 전송하게 되어 날씨 데이터를 정상적으로 조회할 수 없습니다.
해결 방안
한국 시간대(Asia/Seoul)를 명시적으로 지정하여 현재 시간을 가져오도록 수정해야 합니다.
| LocalDateTime now = adjustBaseTime(LocalDateTime.now()); | |
| LocalDateTime now = adjustBaseTime(LocalDateTime.now(java.time.ZoneId.of("Asia/Seoul"))); |
| @Bean | ||
| public WebClient weatherApiWebClient() { | ||
| return WebClient.builder() | ||
| .baseUrl("https://apis.data.go.kr/1360000/VilageFcstInfoService_2.0") | ||
| .build(); | ||
| } |
There was a problem hiding this comment.
[P2] 기상청 API Service Key 더블 인코딩(Double Encoding) 방지
공공데이터포털에서 발급받는 기상청 API의 serviceKey는 이미 인코딩된 상태로 제공되는 경우가 많습니다.
문제점
WebClient는 기본적으로 URI를 빌드할 때 쿼리 파라미터를 자동으로 인코딩합니다. 이로 인해serviceKey가 이중으로 인코딩(Double Encoding)되어 기상청 API 측에서SERVICE_KEY_IS_NOT_REGISTERED_ERROR(등록되지 않은 서비스키) 에러를 반환할 수 있습니다.
해결 방안
weatherApiWebClient 빈 등록 시 DefaultUriBuilderFactory의 인코딩 모드를 NONE으로 설정하여 이중 인코딩을 방지하는 것이 안전합니다. 기상청 API에 전달하는 다른 파라미터(날짜, 시간, 격자 좌표 등)는 단순 숫자이므로 인코딩을 하지 않아도 무방합니다.
@Bean
public WebClient weatherApiWebClient() {
org.springframework.web.util.DefaultUriBuilderFactory factory = new org.springframework.web.util.DefaultUriBuilderFactory("https://apis.data.go.kr/1360000/VilageFcstInfoService_2.0");
factory.setEncodingMode(org.springframework.web.util.DefaultUriBuilderFactory.EncodingMode.NONE);
return WebClient.builder()
.uriBuilderFactory(factory)
.build();
}| Map<String, Object> items = (Map<String, Object>) body.get("items"); | ||
| if (items == null) return Optional.empty(); | ||
|
|
||
| List<Map<String, Object>> itemList = (List<Map<String, Object>>) items.get("item"); | ||
| if (itemList == null) return Optional.empty(); |
There was a problem hiding this comment.
[P3] 기상청 API 응답 데이터 타입 캐스팅 예외 방지 (Defensive Programming)
기상청 API는 데이터가 없거나 에러가 발생했을 때, items 필드를 객체(Map)가 아닌 빈 문자열("")로 반환하는 일관성 없는 응답 스펙을 가지고 있습니다.
문제점
items가 빈 문자열("")로 반환될 경우,(Map<String, Object>) body.get("items")코드에서ClassCastException이 발생합니다.- 비록
getTemperature메서드 전체가try-catch로 감싸져 있어 시스템이 다운되지는 않지만, 불필요한 예외 발생 및 경고 로그가 남게 됩니다.
해결 방안
items 필드의 타입을 안전하게 검증한 후 캐스팅하도록 방어적 코드를 추가하는 것을 권장합니다.
| Map<String, Object> items = (Map<String, Object>) body.get("items"); | |
| if (items == null) return Optional.empty(); | |
| List<Map<String, Object>> itemList = (List<Map<String, Object>>) items.get("item"); | |
| if (itemList == null) return Optional.empty(); | |
| Object itemsObj = body.get("items"); | |
| if (!(itemsObj instanceof Map)) return Optional.empty(); | |
| Map<String, Object> items = (Map<String, Object>) itemsObj; | |
| List<Map<String, Object>> itemList = (List<Map<String, Object>>) items.get("item"); | |
| if (itemList == null) return Optional.empty(); |
🧾 요약
🔗 이슈
✨ 변경 내용
hiking_records테이블에temperature컬럼 추가 (Flyway V27)HikingRecord엔티티 및HikingRecordDetailResponse응답에temperature필드 추가GridConverter) 구현WeatherService) 구현 (3초 타임아웃)complete) 시 기온 조회 → 등산 기록에 저장하는 흐름 연결✅ 확인
Summary by CodeRabbit