Skip to content

Conversation

@daeun-han
Copy link
Member

@daeun-han daeun-han commented Feb 1, 2026

📌 PR 제목

[feat] 홈 화면 '내 산책 정보' 조회 API 구현 및 성능 최적화


✨ 요약 설명

사용자가 홈 화면에 진입 할 때 마주하는 '이번 달 누적 산책 거리, 시간, 횟수'를 확인할 수 있는 API를 구현했습니다. 데이터 조회 성능을 위해 Redis 캐싱을 적용했으며, 데이터 정합성을 보장하기 위한 로직 개선과 성능 테스트를 완료했습니다.


🧾 변경 사항

  • API: 홈 화면 산책 정보 조회 API 구현 (GET /api/v1/home/info)
  • Querydsl: RouteQueryRepository를 통해 이번 달 산책 통계를 산출하는 쿼리 구현
  • Cache: Redis 캐싱 적용 (@Cacheable) 및 산책 기록 저장 시 캐시 무효화 (@CacheEvict) 로직 추가
  • Fix: 산책 기록 저장 시 LineString SRID(4326) 누락으로 인한 DB 저장 오류 수정
  • Test: 캐시 정합성 및 데이터 저장을 검증하는 통합 테스트 코드 추가

📂 PR 타입

  • 기능 추가
  • 버그 수정
  • 리팩토링
  • 문서 수정
  • 기타 (직접 작성: 테스트 코드 추가)

🧪 테스트

  • Postman / Swagger를 통한 API 응답 확인
  • 로컬 환경에서 가상 데이터 만 건을 활용한 Query 성능 테스트 완료

데이터 1건 데이터 10,000건
최적 경로 선택 (Seq Scan)
데이터가 적어 인덱스 없이
전체를 읽는 것이 빠르다고 판단함
(Execution Time: 0.044ms)
인덱스 효용성 검증 (Index Scan)
가상 데이터 주입 시 설계한
복합 인덱스가 정상 작동함을 확인
(Execution Time: 4.751ms)

✅ 체크리스트

  • 깃 & 코드 컨벤션을 지켰는가?
  • Swagger/문서화는 최신 상태인가?
  • 기능 변경 시 영향 받는 모듈을 확인했는가?

💬 리뷰어에게 전달할 말

  • 홈 화면은 사용자 진입이 가장 잦은 곳이기에 SUM 연산이 포함된 통계 쿼리를 매번 날리는 것은 비효율적이라 판단했습니다. 따라서 Redis를 통해 조회 결과를 캐싱하고, 산책을 마치는 시점에만 캐시를 삭제(Evict)하여 실시간성과 성능을 확보하도록 했습니다.
  • 산책 저장 시 캐시가 정상적으로 무효화되는지, 그리고 좌표계(SRID) 에러 없이 데이터가 저장되는지 확인하기 위해 통합 테스트를 작성했습니다. 이때, 테스트용 데이터 생성을 위해 Fixtures 아키텍처를 처음 활용해 보았는데, 프로젝트 컨벤션에 비추어 개선할 점이 있다면 의견 부탁드립니다!
  • DataGrip 실행 계획을 통한 성능 검증도 추가했습니다. 데이터가 쌓였을 때를 대비해 user_id와 created_at 복합 인덱스를 설계하였고, generate_series로 가상 데이터 10,000건 주입 후 EXPLAIN ANALYZE를 실행한 결과, 의도한 대로 Index Scan이 작동하여 4ms대의 응답 속도를 확인했습니다.
  • 제언하자면, 현재 로컬/H2/AWS DB 간 스키마와 기초 데이터를 노션 및 수기로 맞추고 있는데, 추후 형상 관리를 위해 Flyway 도입을 고려해 보면 좋을 것 같습니다.

🔗 관련 이슈

아래 이슈번호 에 번호를 적으면 풀리퀘스트 머지 완료 시 자동으로 해당 이슈가 닫힙니다.

Summary by CodeRabbit

릴리스 노트

  • New Features

    • 홈 화면 산책 정보 조회 API 추가: 사용자의 월간 산책 거리, 시간, 횟수 조회 기능
    • Redis 기반 캐싱 기능 도입으로 조회 성능 개선
  • Improvements

    • 산책 저장 시 홈 정보 캐시 자동 갱신으로 최신 데이터 유지
    • 데이터베이스 인덱스 추가로 조회 속도 최적화

✏️ Tip: You can customize this high-level summary in your review settings.

@daeun-han
Copy link
Member Author

@CodeRabbit review

@coderabbitai
Copy link

coderabbitai bot commented Feb 1, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai
Copy link

coderabbitai bot commented Feb 1, 2026

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

  • 🔍 Trigger a full review

Walkthrough

날씨 도메인을 홈화면 전용 homeWeather 도메인으로 리팩토링하고, 사용자의 월간 누적 산책 정보(거리, 시간, 횟수)를 반환하는 GET /api/v1/home/info 엔드포인트를 추가합니다. QueryDSL 기반 쿼리 최적화, Redis 캐싱, 그리고 데이터베이스 복합 인덱스를 도입합니다.

Changes

Cohort / File(s) Summary
홈 화면 정보 조회 API
src/main/java/org/sopt/pawkey/backendapi/domain/homeWeather/api/controller/HomeController.java, src/main/java/org/sopt/pawkey/backendapi/domain/homeWeather/api/dto/HomeInfoResponseDto.java
홈 화면 정보 조회용 새로운 컨트롤러 엔드포인트와 응답 DTO 추가. 월간 산책 통계(거리, 시간, 횟수)를 반환하고 null 값을 기본값으로 처리합니다.
홈화면 서비스 계층
src/main/java/org/sopt/pawkey/backendapi/domain/homeWeather/application/facade/HomeFacade.java, src/main/java/org/sopt/pawkey/backendapi/domain/homeWeather/application/service/HomeService.java
홈 정보 조회 기능을 처리하는 새로운 파사드 및 서비스 클래스. HomeService는 RouteRepository를 통해 월간 산책 요약을 조회합니다.
쿼리 최적화: QueryDSL 구현
src/main/java/org/sopt/pawkey/backendapi/domain/routes/infra/persistence/RouteQueryRepository.java, src/main/java/org/sopt/pawkey/backendapi/domain/routes/infra/persistence/RouteQueryRepositoryImpl.java
QueryDSL 기반 쿼리 리포지토리 인터페이스 및 구현 추가. JPAQueryFactory를 사용한 효율적인 월간 산책 통계 계산 쿼리.
리포지토리 확장
src/main/java/org/sopt/pawkey/backendapi/domain/routes/domain/repository/RouteRepository.java, src/main/java/org/sopt/pawkey/backendapi/domain/routes/infra/persistence/RouteRepositoryImpl.java
getMonthlyWalkSummary 메서드 추가로 RouteRepository 및 그 구현체 확장.
캐시 설정 및 무효화
src/main/java/org/sopt/pawkey/backendapi/global/config/RedisConfig.java, src/main/java/org/sopt/pawkey/backendapi/domain/routes/application/service/RouteService.java
@EnableCaching 및 RedisCacheManager 빈 추가. RouteService.saveRoute에 @CacheEvict 적용하여 산책 저장 시 홈 정보 캐시 무효화.
데이터베이스 최적화
src/main/java/org/sopt/pawkey/backendapi/domain/routes/infra/persistence/entity/RouteEntity.java
홈 화면 산책 정보 조회 성능을 위한 복합 인덱스(user_id, created_at) 추가.
Weather 도메인 → HomeWeather 도메인 리팩토링
src/main/java/org/sopt/pawkey/backendapi/domain/homeWeather/api/dto/RegionWeatherResponseDTO.java, src/main/java/org/sopt/pawkey/backendapi/domain/homeWeather/api/dto/WeatherMessageResponseDTO.java, src/main/java/org/sopt/pawkey/backendapi/domain/homeWeather/application/dto/result/WeatherMessageResult.java, src/main/java/org/sopt/pawkey/backendapi/domain/homeWeather/application/dto/result/WeatherResult.java, src/main/java/org/sopt/pawkey/backendapi/domain/homeWeather/application/service/WeatherService.java, src/main/java/org/sopt/pawkey/backendapi/domain/homeWeather/domain/model/WeatherMessage.java, src/main/java/org/sopt/pawkey/backendapi/domain/homeWeather/domain/repository/WeatherRepository.java, src/main/java/org/sopt/pawkey/backendapi/domain/homeWeather/domain/service/WeatherCommentaryGenerator.java, src/main/java/org/sopt/pawkey/backendapi/domain/homeWeather/exception/WeatherBusinessException.java, src/main/java/org/sopt/pawkey/backendapi/domain/homeWeather/exception/WeatherErrorCode.java, src/main/java/org/sopt/pawkey/backendapi/domain/homeWeather/infra/cache/WeatherCacheManager.java, src/main/java/org/sopt/pawkey/backendapi/domain/homeWeather/infra/external/WeatherApiClient.java, src/main/java/org/sopt/pawkey/backendapi/domain/homeWeather/infra/external/WeatherClient.java, src/main/java/org/sopt/pawkey/backendapi/domain/homeWeather/infra/external/dto/OpenWeatherResponseDTO.java, src/main/java/org/sopt/pawkey/backendapi/domain/homeWeather/infra/persistence/MessageRepositoryImpl.java, src/main/java/org/sopt/pawkey/backendapi/domain/homeWeather/infra/persistence/SpringDataMessageRepository.java, src/main/java/org/sopt/pawkey/backendapi/domain/homeWeather/infra/persistence/SpringDataWeatherRepository.java, src/main/java/org/sopt/pawkey/backendapi/domain/homeWeather/infra/persistence/WeatherRepositoryImpl.java, src/main/java/org/sopt/pawkey/backendapi/domain/homeWeather/infra/persistence/entity/MessageEntity.java, src/main/java/org/sopt/pawkey/backendapi/domain/homeWeather/infra/persistence/entity/RainyRangeEntity.java, src/main/java/org/sopt/pawkey/backendapi/domain/homeWeather/infra/persistence/entity/TempRangeEntity.java, src/main/java/org/sopt/pawkey/backendapi/domain/homeWeather/infra/persistence/entity/WeatherEntity.java
weather 도메인에서 homeWeather 도메인으로 패키지 경로 전체 이동. 모든 관련 import 경로 업데이트.
새로운 모델 타입
src/main/java/org/sopt/pawkey/backendapi/domain/homeWeather/domain/model/RainyRange.java, src/main/java/org/sopt/pawkey/backendapi/domain/homeWeather/domain/model/TempRange.java, src/main/java/org/sopt/pawkey/backendapi/domain/homeWeather/domain/repository/MessageRepository.java
새로운 열거형 타입(RainyRange, TempRange) 및 메시지 리포지토리 인터페이스 생성(homeWeather 도메인).
뷰 계층 import 업데이트
src/main/java/org/sopt/pawkey/backendapi/domain/routes/application/facade/command/RouteStartFacade.java, src/main/java/org/sopt/pawkey/backendapi/domain/walkPreparation/api/controller/WalkPreparationController.java
weather 도메인 import를 homeWeather 도메인 import로 업데이트.
기존 Weather 도메인 파일 삭제
src/main/java/org/sopt/pawkey/backendapi/domain/weather/domain/model/RainyRange.java, src/main/java/org/sopt/pawkey/backendapi/domain/weather/domain/model/TempRange.java, src/main/java/org/sopt/pawkey/backendapi/domain/weather/domain/repository/MessageRepository.java
weather 도메인의 구식 파일 삭제(homeWeather 도메인으로 이동 완료).
지역 저장소 확장
src/main/java/org/sopt/pawkey/backendapi/domain/region/domain/RegionRepository.java, src/main/java/org/sopt/pawkey/backendapi/domain/region/infra/persistence/RegionRepositoryImpl.java
RegionRepository에 save 메서드 추가로 지역 엔티티 영속성 지원.
테스트 픽스처 및 통합 테스트
src/test/java/org/sopt/pawkey/backendapi/fixtures/RegionFixture.java, src/test/java/org/sopt/pawkey/backendapi/fixtures/RouteFixture.java, src/test/java/org/sopt/pawkey/backendapi/fixtures/UserFixture.java, src/test/java/org/sopt/pawkey/backendapi/global/cache/CacheIntegrationTest.java
테스트 픽스처(Region, Route, User) 및 캐싱 기능 검증 통합 테스트 추가.
테스트 설정 업데이트
src/test/resources/application-test.yml
H2 데이터베이스를 PostgreSQL 호환 모드로 전환, Redis 설정 구조 업데이트, 타임아웃 값 조정.

Sequence Diagram

sequenceDiagram
    participant Client
    participant HomeController
    participant HomeFacade
    participant HomeService
    participant RouteRepository
    participant RouteQueryRepositoryImpl
    participant Database
    participant Cache as Redis Cache

    Client->>HomeController: GET /api/v1/home/info
    HomeController->>Cache: GET homeInfo::{userId}
    alt Cache Hit
        Cache-->>HomeController: HomeInfoResponseDto
    else Cache Miss
        HomeController->>HomeFacade: getHomeInfo(userId)
        HomeFacade->>HomeService: getHomeInfo(userId)
        HomeService->>RouteRepository: getMonthlyWalkSummary(userId)
        RouteRepository->>RouteQueryRepositoryImpl: getMonthlyWalkSummary(userId)
        RouteQueryRepositoryImpl->>Database: QueryDSL Query<br/>(user_id + created_at >= 1st of month)
        Database-->>RouteQueryRepositoryImpl: distance, duration, count
        RouteQueryRepositoryImpl-->>RouteRepository: HomeInfoResponseDto
        RouteRepository-->>HomeService: HomeInfoResponseDto
        HomeService-->>HomeFacade: HomeInfoResponseDto
        HomeFacade-->>HomeController: HomeInfoResponseDto
        HomeController->>Cache: SET homeInfo::{userId}
        Cache-->>HomeController: OK
    end
    HomeController-->>Client: ApiResponse<HomeInfoResponseDto>
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested reviewers

  • bingle625
🚥 Pre-merge checks | ✅ 4 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed PR 제목은 '홈 화면 내 산책 정보 조회 API 구현 및 성능 최적화'로 전체 변경사항의 핵심(홈 화면 API 구현, 성능 최적화)을 명확하게 요약하고 있습니다.
Linked Issues check ✅ Passed PR이 이슈 #223의 모든 주요 요구사항을 충족합니다: GET /api/v1/home/info 엔드포인트 구현, 월별 산책 데이터 합산 로직(QueryDSL), Response DTO 생성(distance/time/count), 데이터 없을 시 기본값(0,0,0) 반환, 예외 처리 구현 [#223].
Out of Scope Changes check ✅ Passed 모든 변경사항이 홈 화면 API 구현 및 성능 최적화의 범위 내입니다: 홈 정보 조회 기능, 캐싱 인프라, 복합 인덱스, 테스트 코드, SRID 버그 수정 등이 모두 이슈 #223의 목표와 연관되어 있습니다.
Description check ✅ Passed PR 설명이 템플릿의 모든 필수 섹션을 포함하고 있으며, 구체적인 변경 사항, 기술적 구현 상세, 테스트 결과, 성능 분석이 명확하게 작성되어 있습니다.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/#223

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In
`@src/main/java/org/sopt/pawkey/backendapi/domain/homeWeather/api/dto/HomeInfoResponseDto.java`:
- Around line 3-15: Remove the unnecessary Jackson polymorphic annotation from
the HomeInfoResponseDto: delete the import
com.fasterxml.jackson.annotation.JsonTypeInfo; and remove the `@JsonTypeInfo`(use
= JsonTypeInfo.Id.CLASS, property = "@class") annotation placed above the public
record HomeInfoResponseDto(...); keep the record and its compact constructor
that defaults null distance/totalTime/count to 0.0/0/0 unchanged so behavior
remains the same while preventing internal class name exposure in JSON
responses.

In
`@src/main/java/org/sopt/pawkey/backendapi/domain/routes/infra/persistence/RouteQueryRepositoryImpl.java`:
- Around line 3-34: getMonthlyWalkSummary uses LocalDate.now() which relies on
JVM default timezone and can miscalculate month boundaries; replace that with an
explicit timezone-aware computation (e.g., ZoneId.of("Asia/Seoul")). In the
getMonthlyWalkSummary method, compute the month start using
ZonedDateTime.now(ZoneId.of("Asia/Seoul")).withDayOfMonth(1).truncatedTo(ChronoUnit.DAYS).toLocalDateTime()
(or equivalent) and use that value in the route.createdAt.goe(startOfMonth)
predicate so the createdAt filter and startOfMonth use the same, explicit
timezone; update the startOfMonth variable and imports accordingly in
RouteQueryRepositoryImpl and ensure references to startOfMonth remain unchanged.
🧹 Nitpick comments (3)
src/main/java/org/sopt/pawkey/backendapi/global/config/RedisConfig.java (1)

54-66: 월간 집계 캐시의 만료/키 설계를 확인해 주세요.

현재 기본 CacheConfig에 TTL이 없어서 @Cacheable 키에 연-월이 포함되지 않으면 월 변경 후에도 이전 월 데이터가 유지될 수 있습니다. 홈 화면 월간 집계 캐시가 월 경계에서 갱신되도록 키에 YearMonth를 포함하거나 TTL을 설정했는지 확인 부탁드립니다.

src/main/java/org/sopt/pawkey/backendapi/domain/routes/domain/repository/RouteRepository.java (1)

5-13: 레포지토리가 API DTO에 직접 의존합니다.

도메인 레포지토리가 HomeInfoResponseDto를 반환하면 계층 결합이 생깁니다. 도메인/애플리케이션 레벨의 요약 모델(예: MonthlyWalkSummary)을 반환하고, 응답 DTO로의 매핑은 서비스/파사드에서 처리하는 구조를 권장합니다.

src/test/java/org/sopt/pawkey/backendapi/global/cache/CacheIntegrationTest.java (1)

1-55: Redis 상태 정리로 테스트 플래키 방지
Redis 키가 테스트 간 남으면 간헐 실패 가능성이 있어, 종료 시 키 정리(또는 테스트 전 flush)를 넣는 게 안전합니다.

🧹 예시 수정안
-import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.Test;
@@
 class CacheIntegrationTest {
@@
 	`@Autowired`
 	private RedisTemplate<String, Object> redisTemplate;
+	private String cacheKey;
@@
-		String cacheKey = "homeInfo::" + userId;
+		cacheKey = "homeInfo::" + userId;
 		assertThat(redisTemplate.hasKey(cacheKey)).isTrue();
@@
 		assertThat(redisTemplate.hasKey(cacheKey)).isFalse();
 	}
+
+	`@AfterEach`
+	void clearCache() {
+		if (cacheKey != null) {
+			redisTemplate.delete(cacheKey);
+		}
+	}
 }

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[FEATURE] <홈화면> 내 산책 정보 제공 API

2 participants