Skip to content

[Feature] 맛집 상세 조회 api 구현#164

Merged
kyer5 merged 5 commits intodevelopfrom
feature/restaurant-detail
May 3, 2026
Merged

[Feature] 맛집 상세 조회 api 구현#164
kyer5 merged 5 commits intodevelopfrom
feature/restaurant-detail

Conversation

@kyer5
Copy link
Copy Markdown
Member

@kyer5 kyer5 commented May 3, 2026

🌱 관련 이슈

📌 작업 내용

  • Kakao panel3 응답에서 station_simple_name 파싱 로직 추가
  • 맛집 상세 조회 API GET /restaurants/{restaurantId} 구현

📝 참고

  • DB 마이그레이션: V5__add_station_to_restaurant.sql (station VARCHAR(30) 추가)

💬 리뷰 요구사항(선택)

@kyer5 kyer5 self-assigned this May 3, 2026
@kyer5 kyer5 added 💫 feat 기능 구현 이슈 🥇 P1 급하고 꼭 필요한 이슈 🐑 kyer5 예린 labels May 3, 2026
@github-actions github-actions Bot requested a review from char-yb May 3, 2026 12:30
Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

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

Code Review

이번 PR은 맛집 상세 정보를 조회하는 API 기능을 추가하고, 관련 데이터 동기화 로직에 '지하철역(station)' 필드를 포함하는 변경사항을 담고 있습니다. 전반적으로 레이어드 아키텍처와 Record 패턴을 잘 활용하여 구현되었습니다. 다만, 데이터베이스 조회 성능 최적화를 위해 Region 정보 조회 시 발생하는 N+1 문제를 해결해야 하며, Admin 전용 쿼리 재사용에 따른 비효율성 개선이 필요합니다. 또한, 코드 중복 제거와 예외 상황(404 Not Found)에 대한 테스트 케이스 보강을 통해 코드의 완성도와 안정성을 높일 수 있습니다.

entity.getName(),
entity.getStation(),
entity.getAddress(),
resolveRegion(entity.getRegionId()),
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

high

[🟠 P1] Performance: Region 정보 조회 시 추가 쿼리 발생 (N+1 위험)

문제점:
findRestaurantDetailById 메서드 내에서 resolveRegion(entity.getRegionId())을 호출하고 있습니다. resolveRegion은 내부적으로 regionJpaRepository.findByIdAndDeletedAtIsNull을 호출하여 별도의 SELECT 쿼리를 실행합니다.

위험성:
현재는 단일 맛집 조회이므로 1번의 추가 쿼리만 발생하지만, 이러한 패턴이 목록 조회 로직 등에 확산될 경우 심각한 N+1 성능 문제를 야기할 수 있습니다.

제안:

  • QueryDSL 쿼리 단계에서 regionEntity를 조인하여 한 번의 쿼리로 모든 데이터를 가져오도록 개선하십시오.

예시:

// ❌ Bad
resolveRegion(entity.getRegionId())

// ✅ Good
// 쿼리에서 .leftJoin(regionEntity).on(restaurantEntity.regionId.eq(regionEntity.id)) 추가 후
// regionEntity.code를 통해 Region 객체 생성

Comment on lines +348 to +350
Tuple tuple = createAdminRestaurantTupleQuery(RestaurantAdminListCriteria.of(null, null, null, null))
.where(restaurantEntity.id.eq(restaurantId))
.fetchOne();
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

[🟡 P2] Architecture & Efficiency: Admin 전용 쿼리 재사용 및 불필요한 데이터 조회

문제점:
공용 API인 맛집 상세 조회를 구현하면서 Admin 전용으로 설계된 createAdminRestaurantTupleQuery를 재사용하고 있습니다. 이로 인해 결과 DTO에서 사용하지 않는 mediumCategory까지 조회하게 되며, Admin 전용 필터 로직에 결합되는 문제가 있습니다.

제안:

  • 공용 상세 조회를 위한 전용 쿼리 메서드를 작성하거나, Projections를 사용하여 필요한 필드만 선택적으로 조회하도록 개선하십시오.

예시:

// ❌ Bad
Tuple tuple = createAdminRestaurantTupleQuery(RestaurantAdminListCriteria.of(null, null, null, null))
        .where(restaurantEntity.id.eq(restaurantId))
        .fetchOne();

// ✅ Good
RestaurantDetailResult result = jpaQueryFactory
        .select(Projections.constructor(RestaurantDetailResult.class, ...))
        .from(restaurantEntity)
        .where(restaurantEntity.id.eq(restaurantId))
        .fetchOne();

entity.getRepresentativeReview(),
entity.getReviewCount(),
entity.getAiMateSummaryTitle(),
parseAiMateSummaryContents(entity.getAiMateSummaryContents())
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

[🟡 P2] Maintainability: 중복된 파싱 로직 존재

문제점:
parseAiMateSummaryContents 메서드가 RestaurantCoreRepositoryRestaurantEntity 양쪽에 동일하게 중복 구현되어 있습니다.

제안:

  • RestaurantEntity에 이미 정의된 정적 메서드를 활용하거나, 공통 유틸리티 클래스로 추출하여 코드 중복을 제거하십시오.

예시:

// ❌ Bad
parseAiMateSummaryContents(entity.getAiMateSummaryContents())

// ✅ Good
RestaurantEntity.parseAiMateSummaryContents(entity.getAiMateSummaryContents())

Comment on lines +41 to +69
void getRestaurantDetail_ShouldReturnOk() throws Exception {
when(restaurantFacade.getRestaurantDetailBy(1L)).thenReturn(RestaurantDetailResult.of(
1L,
"맛집 이름",
"고속터미널역",
"서울 서초구 ...",
Region.GANGNAM,
LargeCategory.KOREAN,
4.6,
"https://img.example.com/restaurant.jpg",
"https://place.map.kakao.com/123",
"맛집 설명",
"MEDIUM",
"된장찌개",
10000,
"대표 리뷰",
120,
"AI 요약 제목",
List.of("요약1", "요약2")
));

mockMvc.perform(get("/api/v1/restaurants/1"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.data.restaurantId").value(1L))
.andExpect(jsonPath("$.data.restaurantName").value("맛집 이름"))
.andExpect(jsonPath("$.data.station").value("고속터미널역"))
.andExpect(jsonPath("$.data.largeCategory").value("KOREAN"))
.andExpect(jsonPath("$.data.representMenuPrice").value(10000));
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

[🟡 P2] Testing: 존재하지 않는 맛집 조회에 대한 테스트 케이스 누락

문제점:
맛집 상세 조회 API 테스트에서 성공 케이스(200 OK)만 다루고 있으며, 맛집이 존재하지 않을 경우(404 Not Found)에 대한 검증이 누락되었습니다.

제안:

  • restaurantFacade.getRestaurantDetailByCustomException(ErrorCode.RESTAURANT_NOT_FOUND)을 던지는 상황을 모킹하여 404 응답이 정상적으로 반환되는지 확인하는 테스트를 추가하십시오.

예시:

@Test
@DisplayName("존재하지 않는 맛집 조회 시 404 에러를 반환한다")
void getRestaurantDetail_NotFound() throws Exception {
    when(restaurantFacade.getRestaurantDetailBy(999L))
            .thenThrow(new CustomException(ErrorCode.RESTAURANT_NOT_FOUND));

    mockMvc.perform(get("/api/v1/restaurants/999"))
            .andExpect(status().isNotFound());
}

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 8ab992312b

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

String priceLevel,
String aiMateSummaryTitle,
List<String> aiMateSummaryContents,
String station,
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Update constructors after adding station field

Adding station to KakaoPlaceDetailData changes the canonical constructor arity, but existing call sites (notably the detailData* fixtures in RestaurantSyncServiceTest) still pass the old argument list. This causes test compilation to fail when running ./gradlew test (or any task that compiles test sources), which blocks CI for this change.

Useful? React with 👍 / 👎.

@kyer5 kyer5 merged commit 58b2130 into develop May 3, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

💫 feat 기능 구현 이슈 🐑 kyer5 예린 🥇 P1 급하고 꼭 필요한 이슈

Projects

None yet

Development

Successfully merging this pull request may close these issues.

✨ 맛집 상세 정보 API 구현

1 participant