Skip to content

Conversation

@leegwichan
Copy link
Member

@leegwichan leegwichan commented Aug 17, 2025

✨ 개요

  • 최신 가게 검색 API에 응원 내용 추가
    Image

🧾 관련 이슈

closed #173

🔍 참고 사항 (선택)

  • 빠르게 작업하기 위해서 @OneToMany + @EntityGraph를 이용하여 N+1 문제를 일부 처리했습니다.

Summary by CodeRabbit

  • 새로운 기능
    • 매장 목록/프리뷰 응답에 cheerDescriptions(응원 메시지 리스트) 추가로 각 매장의 응원 문구 확인 가능.
  • 리팩터
    • 목록 조회 시 응원 데이터 함께 로드 및 조회 전용 트랜잭션 적용으로 응답 일관성·성능 개선.
  • 문서
    • 매장 목록 응답 스키마에 stores[].cheerDescriptions 필드 추가 반영.
  • 테스트
    • 다중 응원자, 페이징, 이미지 조회 및 예외 시나리오 검증을 포함해 테스트 보강; 파일 업로드용 프리사인드 URL 모킹 추가.

@coderabbitai
Copy link

coderabbitai bot commented Aug 17, 2025

Caution

Review failed

The pull request is closed.

Walkthrough

최신 상점 조회 응답에 응원 문자열 목록(cheerDescriptions)을 포함하도록 DTO·도메인·레포지토리·서비스·테스트를 변경했다. Store에 Cheer 일대다 연관을 추가하고 조회 시 cheers를 즉시 로딩해 응원 문자열을 응답에 포함한다.

Changes

Cohort / File(s) Summary
API DTO 확장
src/main/java/eatda/controller/store/StorePreviewResponse.java
StorePreviewResponse 레코드에 List<String> cheerDescriptions 컴포넌트 추가; 기존 생성자 호출에 store.getCheerDescriptions() 전달; java.util.List import 추가.
도메인: 연관 및 집계
src/main/java/eatda/domain/store/Store.java
@OneToMany(mappedBy = "store") private List<Cheer> cheers 필드 추가 및 public List<String> getCheerDescriptions() 메서드 추가(cheer의 description 수집).
레포지토리: 페치 전략
src/main/java/eatda/repository/store/StoreRepository.java
리스트 조회 메서드들(findAllByOrderByCreatedAtDesc, findAllByCategoryOrderByCreatedAtDesc)에 @EntityGraph(attributePaths = {"cheers"}) 추가로 cheers 즉시 로딩 지정.
서비스: 트랜잭션·이미지 조회 변경
src/main/java/eatda/service/store/StoreService.java
getStores(...), getStoreImages(...)@Transactional(readOnly = true) 추가. getStoreImages는 저장소 엔티티를 조회한 뒤 cheerImageRepository.findAllByCheer_StoreOrderByOrderIndexAsc(store) 호출로 변경.
레포지토리(cheer)
src/main/java/eatda/repository/cheer/CheerImageRepository.java
findAllByCheer_Store_IdOrderByOrderIndexAsc(Long storeId)findAllByCheer_StoreOrderByOrderIndexAsc(Store store)로 시그니처 변경(파라미터 타입 Long → Store).
테스트 및 문서 업데이트
src/test/java/.../StoreDocumentTest.java, src/test/java/.../StoreServiceTest.java, src/test/java/.../StoreControllerTest.java, src/test/java/.../CheerGenerator.java, src/test/java/.../BaseControllerTest.java
테스트 코드와 REST Docs를 cheerDescriptions에 맞게 갱신. CheerGenerator의 한 오버로드 제거로 호출부 변경. BaseControllerTest에 FileClient 목 추가 및 presigned URL 스텁 추가. 여러 단위/통합 테스트에 cheerDescriptions와 이미지 조회 관련 검증 추가 및 시그니처/호출 정리.

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant Controller as StoreController
  participant Service as StoreService
  participant Repo as StoreRepository
  participant DB

  Client->>Controller: GET /stores?...
  Controller->>Service: getStores(page,size,category)
  Service->>Repo: findAllByOrderByCreatedAtDesc(Pageable) [@EntityGraph(cheers)]
  Repo->>DB: Query Stores + Cheers
  DB-->>Repo: Stores with Cheers
  Service->>Service: map Store -> StorePreviewResponse(..., cheerDescriptions)
  Service-->>Controller: StoresResponse (includes cheerDescriptions)
  Controller-->>Client: 200 OK
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Assessment against linked issues

Objective Addressed Explanation
최신 상점 조회 API에 응원 내용 추가 (#173)

Assessment against linked issues: Out-of-scope changes

Code Change Explanation
서비스 메서드에 읽기 전용 트랜잭션 추가 (src/main/java/eatda/service/store/StoreService.java) 트랜잭션 속성 변경은 이슈의 "응원 내용 추가" 목표와 직접 관련되지 않음.
CheerImageRepository 메서드 시그니처 변경 (src/main/java/eatda/repository/cheer/CheerImageRepository.java) 이미지 조회 파라미터 타입 변경은 응원 문자열 노출 목표와 직접적 관련이 없음.
테스트에 FileClient 목 추가 (src/test/java/eatda/controller/BaseControllerTest.java) presigned URL 목 추가는 문서/테스트 환경 설정으로 보이며 이슈 목표에 명시되지 않음.

"깡충, 응원 모아왔어요!" 🥕
새 상점 카드에 웃음 실어 보냈지요.
한 줄 한 줄 모여 춤추는 cheer들,
당근 들고 기쁘게 전하는 토끼의 박수—
출시의 밭에 꽃피운 작은 응원들.

Tip

🔌 Remote MCP (Model Context Protocol) integration is now available!

Pro plan users can now connect to remote MCP servers from the Integrations page. Connect with popular remote MCPs such as Notion and Linear to add more context to your reviews and chats.


📜 Recent review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 686a4f9 and 17a10e3.

📒 Files selected for processing (6)
  • src/main/java/eatda/repository/cheer/CheerImageRepository.java (1 hunks)
  • src/main/java/eatda/service/store/StoreService.java (2 hunks)
  • src/test/java/eatda/controller/BaseControllerTest.java (3 hunks)
  • src/test/java/eatda/controller/store/StoreControllerTest.java (4 hunks)
  • src/test/java/eatda/fixture/CheerGenerator.java (0 hunks)
  • src/test/java/eatda/service/store/StoreServiceTest.java (6 hunks)
✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/PRODUCT-259

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
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

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: 0

🧹 Nitpick comments (6)
src/main/java/eatda/domain/store/Store.java (2)

60-62: OneToMany 컬렉션 초기화 누락 — 새 엔티티/테스트 컨텍스트에서 NPE 가능성

JPA가 영속 컨텍스트에서 컬렉션 프록시를 주입하더라도, 새로 빌더로 생성된 Store 등에서는 cheers가 null일 수 있어 getCheerDescriptions()에서 stream() 호출 시 NPE 가능성이 있습니다. 기본 초기화 및 배치 페치 옵션을 함께 고려해 주세요.

두 가지 대안 중 하나를 권장합니다.

  • 대안 A: 컬렉션만 초기화
-    @OneToMany(mappedBy = "store")
-    private List<Cheer> cheers;
+    @OneToMany(mappedBy = "store")
+    private List<Cheer> cheers = new ArrayList<>();

추가 import:

  • import java.util.ArrayList;

  • 대안 B: N+1 완화까지 고려 (Hibernate 의존)

-    @OneToMany(mappedBy = "store")
-    private List<Cheer> cheers;
+    @OneToMany(mappedBy = "store")
+    @BatchSize(size = 100)
+    private List<Cheer> cheers = new ArrayList<>();

추가 import:

  • import org.hibernate.annotations.BatchSize;
  • import java.util.ArrayList;

orphanRemoval/cascade 설정은 도메인 요구사항(응원 삭제 정책)에 따라 별도 검토가 필요합니다.


97-101: Null-safe 처리 및 불필요한 null 설명 제거 제안

cheers가 null일 가능성에 대비하고, null 설명은 제외하면 소비자 입장에서 안정적입니다.

다음과 같이 방어적으로 수정해 주세요.

-    public List<String> getCheerDescriptions() {
-        return cheers.stream()
-                .map(Cheer::getDescription)
-                .toList();
-    }
+    public List<String> getCheerDescriptions() {
+        List<Cheer> list = (cheers == null) ? List.of() : cheers;
+        return list.stream()
+                .map(Cheer::getDescription)
+                .filter(Objects::nonNull)
+                .toList();
+    }

추가 import:

  • import java.util.Objects;

추가로, 빈 문자열 제거가 필요하다면 .filter(s -> !s.isBlank())를 덧붙일 수 있습니다.

src/main/java/eatda/controller/store/StorePreviewResponse.java (1)

16-25: 미리보기 응답의 페이로드 제어(선택) — 상위 N개만 노출 고려

스토어별 응원 수가 많을 경우 응답 페이로드가 커질 수 있습니다. 미리보기 성격이라면 상위 N개(예: 3개)만 노출을 고려해 보세요. 구현 위치는 도메인 getCheerDescriptions() 또는 본 생성자 중 한 곳으로 일관되게 적용하면 됩니다.

가능한 간단한 적용 예:

-                store.getCheerDescriptions()
+                store.getCheerDescriptions().stream().limit(3).toList()

정렬(최신순 등)이 필요하다면 Cheer의 생성/작성 시각을 기준으로 정렬 후 제한을 적용하세요.

src/test/java/eatda/service/store/StoreServiceTest.java (1)

80-87: 검증 강도 보강 제안: 응원 내용 자체도 검증(선택)

현재는 개수만 검증합니다. 매핑 정확도를 올리려면 응원 텍스트의 내용(예: not blank, 특정 프리픽스 등)까지 점검하면 회귀에 강합니다.

예:

assertThat(response.stores().get(0).cheerDescriptions())
    .allMatch(s -> s != null && !s.isBlank());

또는 테스트 픽스처에서 명시적 설명을 넣고 containsExactlyInAnyOrder(...)로 검증할 수 있습니다.

src/main/java/eatda/repository/store/StoreRepository.java (1)

24-28: Pageable + @entitygraph(to-many) 사용 시 페이지 왜곡/중복 가능성 주의

@EntityGraph(attributePaths = {"cheers"})가 to-many 관계를 페치 조인 형태로 강제할 수 있어 페이징 시 중복/왜곡(또는 메모리 내 distinct) 이슈가 발생할 수 있습니다. 데이터가 늘어나면 성능/정합성 문제가 생길 수 있으니 확인 바랍니다.

검토/대안:

  • 단건/페이지 조회는 루트 엔티티만 페치하고, 두 번째 쿼리로 IN (:storeIds) 형태로 cheers를 일괄 로딩해 맵핑(응원 텍스트만 필요 시 projection)합니다.
  • 또는 @BatchSize(size = N)(Hibernate) + LAZY 유지로 컬렉션 배치 로딩을 활용합니다.
  • 꼭 그래프가 필요하면 to-one만 그래프로 가져오고 to-many는 별도 경로로 로딩하세요.

증상 확인 방법:

  • 다수 응원(cheers)이 달린 가게가 섞인 상태에서 페이지 경계(예: size=2) 테스트 시, 결과 수/정렬이 의도와 다른지 로그 SQL로 확인.
src/test/java/eatda/document/store/StoreDocumentTest.java (1)

119-121: cheerDescriptions 필드의 이미지 포함 여부 검증 및 문서화 개선
확인 결과 StorePreviewResponse.cheerDescriptionsList<String>으로 문자열만 반환되며, 이미지 URL은 포함되어 있지 않습니다. API 의도에 따라 아래 중 하나를 선택해주세요:

• 옵션 A: 이미지 URL도 함께 노출하도록 스키마를 객체 배열로 확장

- fieldWithPath("stores[].cheerDescriptions").type(ARRAY).description("음식점에 달린 응원 메시지")
+ fieldWithPath("stores[].cheers").type(ARRAY).description("음식점에 달린 응원 리스트"),
+ fieldWithPath("stores[].cheers[].description").type(STRING).description("응원 메시지"),
+ fieldWithPath("stores[].cheers[].imageUrl").type(STRING).description("응원 이미지 URL").optional()

• 옵션 B: 텍스트만 제공하는 게 의도라면, 설명에 “이미지는 포함되지 않음”을 명시

- fieldWithPath("stores[].cheerDescriptions").type(ARRAY).description("음식점에 달린 응원 메시지")
+ fieldWithPath("stores[].cheerDescriptions").type(ARRAY).description("음식점에 달린 응원 메시지 텍스트 목록(이미지는 포함되지 않음)")

대상 파일 및 위치

  • src/test/java/eatda/document/store/StoreDocumentTest.java:120
📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 02b8b59 and b071fbc.

📒 Files selected for processing (6)
  • src/main/java/eatda/controller/store/StorePreviewResponse.java (2 hunks)
  • src/main/java/eatda/domain/store/Store.java (4 hunks)
  • src/main/java/eatda/repository/store/StoreRepository.java (2 hunks)
  • src/main/java/eatda/service/store/StoreService.java (2 hunks)
  • src/test/java/eatda/document/store/StoreDocumentTest.java (2 hunks)
  • src/test/java/eatda/service/store/StoreServiceTest.java (1 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (1)
src/main/java/eatda/domain/store/Store.java (2)
src/main/java/eatda/domain/story/Story.java (1)
  • Table (27-171)
src/main/java/eatda/domain/member/Member.java (1)
  • Table (18-119)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: test
🔇 Additional comments (5)
src/main/java/eatda/service/store/StoreService.java (2)

39-39: 읽기 전용 트랜잭션 지정 LGTM

조회 전용 메서드에 @Transactional(readOnly = true) 적용으로 성능 최적화 및 실수로 인한 쓰기 방지에 도움이 됩니다.


55-55: 읽기 전용 트랜잭션 지정 LGTM

이미지 조회도 읽기 전용으로 적절합니다.

src/main/java/eatda/controller/store/StorePreviewResponse.java (1)

12-14: 응답 필드 확장 LGTM

cheerDescriptions 추가가 PR 목적과 일치하며 역호환성(필드 추가) 측면에서도 안전합니다.

src/test/java/eatda/service/store/StoreServiceTest.java (1)

62-74: 테스트 데이터 시나리오 개선 LGTM

복수 회원/가게로 응원 관계를 구성하여 시나리오 현실성이 좋아졌습니다.

src/test/java/eatda/document/store/StoreDocumentTest.java (1)

129-133: StorePreviewResponse에 cheerDescriptions 샘플 데이터 추가 LGTM

테스트 더미 데이터가 새로운 시그니처(List cheerDescriptions)를 잘 반영합니다. 문서화 필드와 일관성도 좋아 보입니다.

Copy link
Member

@lvalentine6 lvalentine6 left a comment

Choose a reason for hiding this comment

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

이번 PR도 고생하셨습니다! 🎉
궁금증 몇개만 코멘트 남겼습니다!


assertAll(
() -> assertThat(response.stores()).hasSize(size),
() -> assertThat(response.stores()).hasSize(2),
Copy link
Member

Choose a reason for hiding this comment

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

위에 size 선언은 안쓰게 되나요??

Copy link
Member Author

Choose a reason for hiding this comment

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

위의 size 값을 쓰도록 변경했습니다.

return new StoreResponse(store);
}

// TODO : N+1 문제 해결
Copy link
Member

Choose a reason for hiding this comment

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

EntityGraph를 사용했어도 아직 N + 1 문제가 남아있나요??

Copy link
Member Author

Choose a reason for hiding this comment

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

추후 지워질 예정이라 그대로 두겠습니다.

# Conflicts:
#	src/test/java/eatda/service/store/StoreServiceTest.java
@sonarqubecloud
Copy link

@leegwichan leegwichan merged commit 1ad376d into develop Aug 19, 2025
3 of 4 checks passed
@leegwichan leegwichan deleted the feat/PRODUCT-259 branch August 19, 2025 02:33
@github-actions
Copy link

🎉 This PR is included in version 1.4.0-develop.91 🎉

The release is available on GitHub release

Your semantic-release bot 📦🚀

@github-actions
Copy link

🎉 This PR is included in version 1.8.0-develop.1 🎉

The release is available on GitHub release

Your semantic-release bot 📦🚀

@github-actions
Copy link

🎉 This PR is included in version 1.8.0 🎉

The release is available on GitHub release

Your semantic-release bot 📦🚀

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.

[PRODUCT-259] [Feat] 최신 상점 조회 API의 응원 내용 추가

3 participants