-
Notifications
You must be signed in to change notification settings - Fork 2
[REFACTOR] member 모듈 분리 #52
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
|
Note Other AI code review bot(s) detectedCodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review. Walkthrough멤버 엔티티 참조를 문자열 기반 Changes
Sequence Diagram(s)sequenceDiagram
participant Client
participant API as ReservationService(API)
participant Domain as ReservationDomainService
participant Member as MemberDomainService
participant EventBus
rect rgb(245,250,255)
Client->>API: CreateReservation(request with memberId)
API->>Domain: toDomain(..., memberId)
Domain-->>API: Reservation(domain with memberId)
API->>Member: find/getById(memberId) -- fetch name/contacts --
Member-->>API: Member(name, phone, email, vip)
API->>EventBus: publish ReservationCreatedEvent(reservation, member.name)
EventBus-->>API: ack
end
(유사 흐름이 Update/Confirm/Cancel/Confirmed 이벤트에도 적용 — Reservation은 memberId로 생성되고 이벤트 발행 시 MemberDomainService로부터 memberName을 조회해 포함) Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes
Possibly related PRs
Suggested labels
Suggested reviewers
Poem
Pre-merge checks and finishing touches❌ Failed checks (2 warnings, 1 inconclusive)
✅ Passed checks (2 passed)
✨ 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 @unifolio0, 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! 이 Pull Request는 Highlights
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 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 counter productive. 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. You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension. Footnotes
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Code Review
This pull request focuses on refactoring the member module by separating it into a new domain module (domain-member). This involves creating new entities and services within the new module and updating dependencies and usages across the api-owner, api-user, and batch-reminder modules. The changes include modifications to reservation services and event handling to utilize the new member domain service and entities. The review focuses on ensuring correct implementation, proper dependency management, and adherence to coding standards.
| public ReservationReminderPayload process(Reservation reservation) { | ||
| log.info("Processing reservation reminder for reservation ID: {}", reservation.getId()); | ||
|
|
||
| Member member = memberDomainService.getById(reservation.getMemberId()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fetching the Member entity within the process method of the ItemProcessor can lead to performance issues, especially when processing a large number of reservations. Consider optimizing this by either fetching the member data in the ItemReader or using a batching technique to reduce the number of database calls.
If performance becomes an issue, explore caching strategies or alternative data retrieval methods.
| private double rating; | ||
|
|
||
| public Review(String content, double rating, Situation situation, Restaurant restaurant, Member member) { | ||
| public Review(String content, double rating, Situation situation, Restaurant restaurant, String memberId) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
|
|
||
| public Reservation(Restaurant restaurant, AvailableDate availableDate, | ||
| Member member, int partySize, String specialRequest) { | ||
| String memberId, int partySize, String specialRequest) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
|
|
||
| dependencies { | ||
| implementation project(':domain') | ||
| implementation project(':domain-member') |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This line adds a dependency on the new :domain-member module. Ensure that this dependency is necessary and doesn't introduce any circular dependencies or conflicts with existing dependencies. Also, verify that the correct version of the module is being used if versioning is in place.
If the dependency is not needed, it should be removed to reduce the project's complexity and build time.
| implementation 'org.springframework.boot:spring-boot-starter-validation' | ||
|
|
||
| testImplementation(testFixtures(project(':domain'))) | ||
| testImplementation(testFixtures(project(':domain-member'))) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This line adds test fixtures for the :domain-member module. Confirm that these test fixtures are actually needed for testing in this module. Test fixtures should only be included if they provide necessary testing utilities or data specific to the :domain-member module.
If the test fixtures are not required, removing them will simplify the build configuration and reduce potential conflicts.
|
|
||
| dependencies { | ||
| implementation project(':domain') | ||
| implementation project(':domain-member') |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This line adds a dependency on the new :domain-member module. Ensure that this dependency is necessary and doesn't introduce any circular dependencies or conflicts with existing dependencies. Also, verify that the correct version of the module is being used if versioning is in place.
If the dependency is not needed, it should be removed to reduce the project's complexity and build time.
|
|
||
| ReservationConfirmedEvent event = new ReservationConfirmedEvent(reservation); | ||
| Member member = memberDomainService.getById(reservation.getMemberId()); | ||
| ReservationConfirmedEvent event = new ReservationConfirmedEvent(reservation, member.getName()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The ReservationConfirmedEvent is being published with the member's name. Ensure that all necessary data for the event is being passed and that no sensitive information is inadvertently exposed through this event. Consider if other member details are needed and if they should be included.
Also, verify that the member.getName() call does not introduce a performance bottleneck if this method is frequently called.
| this.restaurant = restaurant; | ||
| this.availableDate = availableDate; | ||
| this.member = member; | ||
| this.memberId = memberId; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| implementation 'org.springframework.boot:spring-boot-starter-validation' | ||
|
|
||
| testImplementation(testFixtures(project(':domain'))) | ||
| testImplementation(testFixtures(project(':domain-member'))) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This line adds test fixtures for the :domain-member module. Confirm that these test fixtures are actually needed for testing in this module. Test fixtures should only be included if they provide necessary testing utilities or data specific to the :domain-member module.
If the test fixtures are not required, removing them will simplify the build configuration and reduce potential conflicts.
| this.situation = situation; | ||
| this.restaurant = restaurant; | ||
| this.member = member; | ||
| this.memberId = memberId; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 5
♻️ Duplicate comments (9)
domain/src/main/java/com/wellmeet/domain/restaurant/review/entity/Review.java (1)
48-50: memberId 참조 무결성 검증 필요
@NotBlank만으로는memberId가 실제로 존재하는 회원을 가리키는지 보장할 수 없습니다. 유효하지 않은memberId로 리뷰가 생성되면 데이터 무결성 문제나 예기치 않은 동작이 발생할 수 있습니다.생성자나 서비스 레이어에서
memberId의 존재 여부를 검증하는 로직 추가를 권장합니다.batch-reminder/src/main/java/com/wellmeet/batch/job/ReservationReminderProcessor.java (1)
23-23: ItemProcessor 내 Member 조회로 인한 성능 우려
process메서드에서 각 예약마다MemberDomainService.getById()를 호출하면 대량 예약 처리 시 성능 문제가 발생할 수 있습니다.ItemReader에서 멤버 데이터를 미리 조인하거나 배치 처리 기법을 고려하시기 바랍니다.api-user/build.gradle (2)
7-7: 의존성 추가가 올바릅니다.
domain-member모듈 의존성이 api-user 모듈에 적절하게 추가되었습니다. 멤버 모듈 분리 작업과 일관성 있는 변경입니다.
16-16: 테스트 픽스처 의존성이 적절합니다.테스트에서 멤버 관련 픽스처를 사용할 수 있도록 설정되었습니다.
api-user/src/main/java/com/wellmeet/reservation/ReservationService.java (3)
38-43: 예약 생성 로직이memberId기반으로 변경되었습니다.
Member객체 대신memberId를toDomain에 전달하고, 이벤트 생성을 위해 멤버 이름을 조회합니다. 과거 리뷰에서 지적된 것처럼, 이벤트에 필요한 데이터와 민감 정보 노출 여부를 확인하시기 바랍니다.
87-88: 예약 업데이트 시 멤버 이름 조회예약 업데이트 이벤트 생성을 위해 멤버를 조회합니다. 과거 리뷰에서 언급된 것처럼, 이벤트 데이터의 적절성과 성능 영향을 확인하시기 바랍니다.
100-101: 예약 취소 시 멤버 이름 조회예약 취소 이벤트 생성을 위해 멤버를 조회합니다. 과거 리뷰에서 언급된 것처럼, 이벤트 데이터의 적절성과 성능 영향을 확인하시기 바랍니다.
domain/src/main/java/com/wellmeet/domain/reservation/entity/Reservation.java (2)
59-61:memberId필드가 String 기반으로 변경되었습니다.
@NotBlank검증이 추가되어 빈 문자열을 방지합니다. 그러나memberId의 형식(예: UUID)에 대한 추가 검증은 없습니다. 과거 리뷰에서 언급된 것처럼, 데이터 무결성을 위해 형식 검증 및 데이터베이스 외래 키 제약 조건이 설정되어 있는지 확인하시기 바랍니다.데이터베이스 제약 조건을 확인하세요:
#!/bin/bash # Description: Check for foreign key constraints on reservation.member_id # Search for foreign key definitions in migration files fd -e sql -x grep -l "reservation" {} \; | xargs grep -A 5 -B 5 "member_id.*REFERENCES\|FOREIGN KEY.*member_id"
66-77: 생성자가String memberId를 받도록 변경되었습니다.
Member객체 대신memberId를 직접 전달받으며, 필드 레벨의@NotBlank검증에 의존합니다. 과거 리뷰에서 언급된 것처럼, 생성자 호출 전에memberId가 적절히 검증되었는지 확인하시기 바랍니다.
🧹 Nitpick comments (1)
api-owner/src/main/java/com/wellmeet/reservation/dto/ReservationResponse.java (1)
25-32: 생성자 파라미터 수가 증가했습니다.모듈 분리로 인해 멤버 세부 정보를 개별 파라미터로 전달해야 하므로 생성자가 5개의 파라미터를 받습니다. 코드는 정상 작동하지만, 향후 빌더 패턴이나 별도의 DTO를 고려하여 가독성을 개선할 수 있습니다.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (25)
.github/pull_request_template.md(1 hunks)api-owner/build.gradle(2 hunks)api-owner/src/main/java/com/wellmeet/global/event/event/ReservationConfirmedEvent.java(1 hunks)api-owner/src/main/java/com/wellmeet/reservation/ReservationService.java(3 hunks)api-owner/src/main/java/com/wellmeet/reservation/dto/ReservationResponse.java(2 hunks)api-user/build.gradle(2 hunks)api-user/src/main/java/com/wellmeet/favorite/FavoriteService.java(2 hunks)api-user/src/main/java/com/wellmeet/global/event/event/ReservationCanceledEvent.java(1 hunks)api-user/src/main/java/com/wellmeet/global/event/event/ReservationCreatedEvent.java(1 hunks)api-user/src/main/java/com/wellmeet/global/event/event/ReservationUpdatedEvent.java(1 hunks)api-user/src/main/java/com/wellmeet/reservation/ReservationService.java(3 hunks)api-user/src/main/java/com/wellmeet/reservation/dto/CreateReservationRequest.java(1 hunks)batch-reminder/build.gradle(1 hunks)batch-reminder/src/main/java/com/wellmeet/batch/job/ReservationReminderProcessor.java(1 hunks)domain-member/build.gradle(1 hunks)domain-member/src/main/java/com/wellmeet/domain/common/BaseEntity.java(1 hunks)domain-member/src/main/java/com/wellmeet/domain/common/WellMeetDomainException.java(1 hunks)domain-member/src/main/java/com/wellmeet/domain/member/entity/FavoriteRestaurant.java(1 hunks)domain/src/main/java/com/wellmeet/domain/member/entity/FavoriteRestaurant.java(0 hunks)domain/src/main/java/com/wellmeet/domain/reservation/entity/Reservation.java(3 hunks)domain/src/main/java/com/wellmeet/domain/reservation/repository/ReservationRepository.java(0 hunks)domain/src/main/java/com/wellmeet/domain/restaurant/review/entity/Review.java(2 hunks)domain/src/testFixtures/java/com/wellmeet/domain/fixture/ReservationGenerator.java(1 hunks)domain/src/testFixtures/java/com/wellmeet/domain/fixture/ReviewGenerator.java(1 hunks)settings.gradle(1 hunks)
💤 Files with no reviewable changes (2)
- domain/src/main/java/com/wellmeet/domain/member/entity/FavoriteRestaurant.java
- domain/src/main/java/com/wellmeet/domain/reservation/repository/ReservationRepository.java
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-10-06T03:23:36.830Z
Learnt from: CR
PR: Team-soft-macaron/WellMeet-Backend#0
File: CLAUDE.md:0-0
Timestamp: 2025-10-06T03:23:36.830Z
Learning: Applies to domain-redis/build.gradle : Include Testcontainers dependencies for Redis tests: org.testcontainers:testcontainers and junit-jupiter 1.19.3
Applied to files:
api-owner/build.gradleapi-user/build.gradle
🧬 Code graph analysis (5)
domain-member/src/main/java/com/wellmeet/domain/member/entity/FavoriteRestaurant.java (3)
domain-member/src/main/java/com/wellmeet/domain/common/BaseEntity.java (1)
Getter(12-24)domain-member/src/main/java/com/wellmeet/domain/member/entity/Member.java (1)
Getter(14-68)domain/src/main/java/com/wellmeet/domain/member/entity/FavoriteRestaurant.java (1)
FavoriteRestaurant(17-40)
domain-member/src/main/java/com/wellmeet/domain/common/BaseEntity.java (4)
domain-member/src/main/java/com/wellmeet/domain/member/entity/FavoriteRestaurant.java (1)
Getter(14-35)domain/src/main/java/com/wellmeet/domain/restaurant/review/entity/Review.java (1)
Getter(23-76)domain-member/src/main/java/com/wellmeet/domain/member/entity/Member.java (1)
Getter(14-68)domain/src/main/java/com/wellmeet/domain/common/BaseEntity.java (1)
BaseEntity(12-24)
batch-reminder/src/main/java/com/wellmeet/batch/job/ReservationReminderProcessor.java (5)
batch-reminder/src/main/java/com/wellmeet/batch/job/ReservationReminderReader.java (1)
Slf4j(19-60)batch-reminder/src/main/java/com/wellmeet/batch/job/NotificationSkipListener.java (1)
Slf4j(13-55)batch-reminder/src/main/java/com/wellmeet/batch/job/ReservationReminderWriter.java (1)
Slf4j(11-26)domain/src/testFixtures/java/com/wellmeet/domain/fixture/ReservationGenerator.java (1)
Component(9-22)domain-member/src/testFixtures/java/com/wellmeet/domain/fixture/MemberGenerator.java (1)
Component(7-20)
api-user/src/main/java/com/wellmeet/favorite/FavoriteService.java (1)
domain/src/main/java/com/wellmeet/domain/member/FavoriteRestaurantDomainService.java (1)
Service(11-37)
domain-member/src/main/java/com/wellmeet/domain/common/WellMeetDomainException.java (2)
domain-member/src/main/java/com/wellmeet/domain/member/exception/MemberException.java (1)
Getter(6-12)domain/src/main/java/com/wellmeet/domain/common/WellMeetDomainException.java (1)
Getter(5-14)
🔇 Additional comments (17)
settings.gradle (1)
4-9: 멀티 모듈 구조 확장 확인member 모듈 분리를 위한
domain-member추가와 함께infra-kafka,batch-reminder모듈도 포함되어 멀티 모듈 구조가 잘 확장되었습니다.api-user/src/main/java/com/wellmeet/reservation/dto/CreateReservationRequest.java (1)
30-38: Member 객체 의존성 제거 확인
toDomain메서드가Member객체 대신String memberId를 받도록 변경되어 도메인 간 결합도가 낮아졌습니다..github/pull_request_template.md (1)
1-2: 이슈 관리 방식 업데이트 확인PR 템플릿이 GitHub Issue 연동 형식으로 업데이트되었습니다.
api-user/src/main/java/com/wellmeet/global/event/event/ReservationUpdatedEvent.java (1)
21-32: 이벤트 생성자 시그니처 업데이트 확인
Member객체 의존성이 제거되고memberId와memberName을 별도로 관리하도록 변경되었습니다.api-user/src/main/java/com/wellmeet/global/event/event/ReservationCreatedEvent.java (1)
21-32: 이벤트 생성자 일관성 확인
ReservationUpdatedEvent와 동일한 방식으로 변경되어 일관성이 유지됩니다.domain/src/main/java/com/wellmeet/domain/restaurant/review/entity/Review.java (1)
54-62: 생성자 시그니처 변경 확인
Member객체 대신String memberId를 받도록 일관성 있게 변경되었습니다. 다만 위에서 언급한 대로memberId검증이 필요합니다.api-owner/build.gradle (2)
16-16: 테스트 픽스처 의존성이 적절합니다.
domain-member모듈의 테스트 픽스처를 사용하는 것은 테스트 코드에서 멤버 관련 데이터를 생성할 때 유용합니다.
7-7: 순환 의존성 확인 완료 - 문제 없음의존성 검증 결과,
domain및domain-member모듈 모두 다른 프로젝트 모듈에 의존하지 않으므로 순환 의존성이 발생할 수 없습니다.api-owner에서domain-member를 추가하는 것은 안전합니다.batch-reminder/build.gradle (1)
7-7: 배치 모듈에 필요한 의존성입니다.배치 리마인더 기능에서 멤버 정보에 접근하기 위해
domain-member모듈 의존성이 필요합니다.domain-member/build.gradle (1)
1-12: 새 모듈 구성이 적절합니다.
domain-member모듈의 빌드 설정이 올바르게 구성되었습니다:
- JPA 및 Validation 스타터 의존성
- 테스트 픽스처 플러그인 활성화
- MySQL 커넥터 런타임 의존성
모든 설정이 도메인 모듈에 필요한 표준 구성입니다.
api-owner/src/main/java/com/wellmeet/reservation/ReservationService.java (2)
20-20: MemberDomainService 주입이 적절합니다.멤버 정보를 조회하기 위한 서비스 의존성이 추가되었습니다.
45-46: 이벤트 생성 로직이 올바르게 변경되었습니다.멤버 이름을 조회하여 이벤트 생성자에 전달하는 방식으로 개선되었습니다. 단일 예약에 대한 조회이므로 성능 문제는 없습니다.
domain/src/testFixtures/java/com/wellmeet/domain/fixture/ReservationGenerator.java (1)
18-19: 테스트 픽스처가 올바르게 리팩토링되었습니다.
Member객체 대신String memberId를 받도록 변경되어 멤버 모듈 분리 목표와 일관성 있게 개선되었습니다. 이는 테스트 시 멤버 객체를 생성할 필요 없이 ID만으로 예약을 생성할 수 있게 합니다.api-user/src/main/java/com/wellmeet/global/event/event/ReservationCanceledEvent.java (1)
21-24: 이벤트 생성자가 적절하게 리팩토링되었습니다.
Member객체 참조를 제거하고memberId와memberName을 직접 사용하도록 변경되었습니다. 이는 멤버 모듈 분리 목표와 일치하며, 이벤트가 멤버 엔티티에 의존하지 않도록 개선합니다.다른 예약 이벤트 클래스(
ReservationCreatedEvent,ReservationUpdatedEvent)와도 일관된 패턴입니다.domain/src/testFixtures/java/com/wellmeet/domain/fixture/ReviewGenerator.java (1)
18-20: 테스트 픽스처 변경이 리팩토링 목표와 일치합니다.
Member객체 대신String memberId를 사용하도록 메서드 시그니처가 업데이트되었으며,Review생성자 호출도 적절하게 조정되었습니다.api-owner/src/main/java/com/wellmeet/global/event/event/ReservationConfirmedEvent.java (1)
21-24: 이벤트 생성자가memberName을 명시적으로 요구합니다.이제 호출자가
Member를 조회하여 이름을 전달해야 합니다. 이 패턴이 여러 이벤트에 일관되게 적용되었는지 확인하시기 바랍니다.api-user/src/main/java/com/wellmeet/favorite/FavoriteService.java (1)
41-41: 즐겨찾기 생성이 String ID 기반으로 변경되었습니다.
Member및Restaurant객체 대신 ID를 사용하여 모듈 분리 목표에 부합합니다.
api-owner/src/main/java/com/wellmeet/reservation/ReservationService.java
Show resolved
Hide resolved
| package com.wellmeet.domain.common; | ||
|
|
||
| import jakarta.persistence.EntityListeners; | ||
| import jakarta.persistence.MappedSuperclass; | ||
| import jakarta.validation.constraints.NotNull; | ||
| import java.time.LocalDateTime; | ||
| import lombok.Getter; | ||
| import org.springframework.data.annotation.CreatedDate; | ||
| import org.springframework.data.annotation.LastModifiedDate; | ||
| import org.springframework.data.jpa.domain.support.AuditingEntityListener; | ||
|
|
||
| @Getter | ||
| @EntityListeners(AuditingEntityListener.class) | ||
| @MappedSuperclass | ||
| public abstract class BaseEntity { | ||
|
|
||
| @NotNull | ||
| @CreatedDate | ||
| private LocalDateTime createdAt; | ||
|
|
||
| @NotNull | ||
| @LastModifiedDate | ||
| private LocalDateTime updatedAt; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
BaseEntity 중복 문제를 해결해야 합니다.
domain 모듈과 domain-member 모듈 모두에 동일한 BaseEntity 클래스가 존재합니다. 이는 다음과 같은 문제를 야기합니다:
- 어떤 BaseEntity를 상속받아야 할지 혼란 발생
- 두 클래스를 동기화해야 하는 유지보수 부담
- 시간이 지나면서 두 구현이 달라질 위험
- 모듈 간 결합도 증가
권장 해결 방안:
- 공통 모듈(예:
domain-common)을 생성하여 BaseEntity를 배치하거나 - 한 모듈에만 BaseEntity를 두고 다른 모듈이 이를 의존하도록 변경
다음 스크립트로 BaseEntity의 중복 사용을 확인할 수 있습니다:
#!/bin/bash
# Description: Find all BaseEntity definitions and their usages
echo "=== Finding BaseEntity definitions ==="
fd -t f "BaseEntity.java" --exec echo "File: {}"
echo -e "\n=== Finding classes that extend BaseEntity ==="
rg -n "extends BaseEntity" -A 1 -B 1🤖 Prompt for AI Agents
In domain-member/src/main/java/com/wellmeet/domain/common/BaseEntity.java lines
1-24: this BaseEntity duplicates a definition in the domain module causing
ambiguity and maintenance risk; remove the duplicate and refactor consumers to
reference a single shared BaseEntity (either move this class to a new/shared
domain-common module and update dependencies/imports across modules, or delete
this file and change domain-member to depend on the existing BaseEntity in the
domain module), update module build files (pom.gradle/settings) to add the
dependency, fix imports in all classes that extended the removed duplicate, and
run the provided script to verify there are no remaining BaseEntity duplicates
or unresolved references.
| package com.wellmeet.domain.common; | ||
|
|
||
| import lombok.Getter; | ||
|
|
||
| @Getter | ||
| public class WellMeetDomainException extends RuntimeException { | ||
|
|
||
| private final int statusCode; | ||
|
|
||
| public WellMeetDomainException(String message, int statusCode) { | ||
| super(message); | ||
| this.statusCode = statusCode; | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
중복된 예외 클래스 정의로 인한 클래스패스 충돌 위험
WellMeetDomainException이 domain 모듈과 domain-member 모듈 양쪽에 동일한 패키지(com.wellmeet.domain.common)로 정의되어 있습니다. 이는 클래스패스 충돌을 유발하여 런타임에 어느 클래스가 로드될지 예측할 수 없게 됩니다.
한 곳에만 정의하거나, 공통 모듈로 분리하여 두 모듈이 의존하도록 구조를 변경해야 합니다.
다음 스크립트로 중복 확인이 가능합니다:
🏁 Script executed:
#!/bin/bash
# WellMeetDomainException 클래스 중복 확인
fd -t f "WellMeetDomainException.java" --exec echo "Found: {}"Length of output: 178
Based on the verification I've conducted, I can now confirm the critical nature of the issue and generate the final rewritten review comment.
중복된 예외 클래스 정의로 인한 클래스패스 충돌 위험
두 모듈에서 동일한 FQCN(com.wellmeet.domain.common.WellMeetDomainException)으로 클래스가 정의되면, 클래스패스에서 먼저 나타나는 JAR의 클래스가 로드되어 예측 불가능한 동작이 발생할 수 있습니다. 런타임에 어느 클래스가 로드될지의 동작은 결정되지 않으며 클래스 로더의 구현에만 의존합니다.
Maven 다중 모듈 프로젝트의 권장 사항은 모듈 간 패키지 충돌을 피하기 위해 패키지를 중첩하지 않고 분리하는 것입니다.
다음 중 하나를 수행하세요:
- 공통 모듈을 생성하고 두 모듈이 의존하도록 구조 변경
- 한 모듈만 클래스를 소유하고 다른 모듈이 의존성으로 참조
- 각 모듈의 패키지 구조를 명확히 분리 (예:
com.wellmeet.domain.*vscom.wellmeet.member.*)
🤖 Prompt for AI Agents
In
domain-member/src/main/java/com/wellmeet/domain/common/WellMeetDomainException.java
lines 1-14: this class duplicates the same FQCN in another module, risking
classpath conflicts; remove the duplicate and resolve ownership by either (1)
extracting the exception into a shared/common module that both modules depend on
and update their pom/gradle to use that artifact, (2) keep the exception in a
single owning module and make the other module declare a dependency on that
module, or (3) rename and relocate the class into a distinct package for this
module (and update imports) to avoid overlapping FQCNs.
| @NotBlank | ||
| @Column(name = "member_id") | ||
| private String memberId; | ||
|
|
||
| @NotBlank | ||
| @Column(name = "restaurant_id") | ||
| private String restaurantId; | ||
|
|
||
| public FavoriteRestaurant(String memberId, String restaurantId) { | ||
| this.memberId = memberId; | ||
| this.restaurantId = restaurantId; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
String ID 기반 설계로 변경되었습니다.
@ManyToOne 관계 대신 String ID를 사용하여 모듈 분리가 달성되었습니다. 그러나 JPA 레벨에서 참조 무결성이 보장되지 않으므로, 데이터베이스 레벨에서 외래 키 제약 조건이 설정되어 있는지 확인하시기 바랍니다.
데이터베이스 마이그레이션 파일에서 외래 키 제약 조건을 확인하세요:
🏁 Script executed:
#!/bin/bash
# Description: Check for foreign key constraints in migration files
# Search for foreign key definitions in Flyway migration files
fd -e sql -x grep -l "FOREIGN KEY\|REFERENCES" {} \; | xargs grep -A 2 -B 2 "favorite_restaurant"Length of output: 412
member_id에 대한 외래 키 제약조건이 누락되었습니다.
마이그레이션 파일 검증 결과, restaurant_id에는 FOREIGN KEY 제약조건이 설정되어 있으나, member_id에는 외래 키 제약조건이 정의되어 있지 않습니다. 이로 인해 삭제된 회원의 ID가 데이터베이스에 고아 레코드로 남을 수 있으므로, 마이그레이션 파일에 FOREIGN KEY (member_id) REFERENCES member (id) 제약조건을 추가하여 참조 무결성을 보장하시기 바랍니다.
🤖 Prompt for AI Agents
In
domain-member/src/main/java/com/wellmeet/domain/member/entity/FavoriteRestaurant.java
around lines 23–34, the member_id column lacks a foreign-key mapping; update the
entity to model an actual relationship (replace or augment the memberId field
with a @ManyToOne to the Member entity and use @JoinColumn(name="member_id",
foreignKey=@ForeignKey(name="fk_favoriterestaurant_member"))) so JPA will
generate the FK, and also update the DB migration script to add CONSTRAINT
fk_favoriterestaurant_member FOREIGN KEY (member_id) REFERENCES member(id) (or
alter the table to add that FK) to ensure DB-level referential integrity.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 4
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (4)
api-owner/src/test/java/com/wellmeet/reservation/ReservationServiceTest.java (1)
46-65: DTO 매핑 검증을 추가해야 합니다.현재 테스트는 예약 목록의 크기만 검증하고(64행), ReservationResponse의 실제 내용은 확인하지 않습니다. 코딩 가이드라인에 따르면 서비스 계층 테스트는 DTO 매핑을 검증해야 합니다.
MemberDomainService를 통해 조회된 회원 정보가 ReservationResponse에 올바르게 매핑되었는지 검증하는 assertion을 추가하세요.
다음과 같이 응답 내용을 검증하도록 개선할 수 있습니다:
List<ReservationResponse> expectedReservations = reservationService.getReservations(restaurant.getId()); assertThat(expectedReservations).hasSize(reservations.size()); +assertThat(expectedReservations) + .extracting("memberName", "partySize") + .containsExactlyInAnyOrder( + tuple(member1.getName(), 4), + tuple(member2.getName(), 2) + );api-owner/src/test/java/com/wellmeet/global/event/listener/ReservationEventListenerTest.java (1)
39-47: null memberId로 인한 테스트 신뢰도 저하 가능createMember()가 영속하지 않으면 member.getId()가 null일 수 있어, 예약 생성과 검증(eq(member.getId())) 모두 null로 일치해 테스트가 허위 성공할 수 있습니다. 명시적 memberId 문자열을 사용하도록 수정하세요.
-Member member = createMember(); -Reservation reservation = createReservation(restaurant, availableDate, member); -ReservationConfirmedEvent event = new ReservationConfirmedEvent(reservation, member.getName()); +Member member = createMember(); +String memberId = "member-1"; +Reservation reservation = createReservation(restaurant, availableDate, memberId); +ReservationConfirmedEvent event = new ReservationConfirmedEvent(reservation, member.getName()); @@ -private Reservation createReservation(Restaurant restaurant, AvailableDate availableDate, Member member) { - return new Reservation(restaurant, availableDate, member.getId(), 4, "request"); +private Reservation createReservation(Restaurant restaurant, AvailableDate availableDate, String memberId) { + return new Reservation(restaurant, availableDate, memberId, 4, "request"); } @@ -verify(kafkaProducerService).sendNotificationMessage( - eq(member.getId()), +verify(kafkaProducerService).sendNotificationMessage( + eq(memberId), any(ReservationConfirmedPayload.class) );As per coding guidelines
Also applies to: 74-76
api-user/src/test/java/com/wellmeet/favorite/FavoriteServiceTest.java (1)
112-120: RemoveFavoriteRestaurant 테스트의 null memberId 위험삭제 케이스도 동일 이슈입니다. 명시적 memberId로 교체하세요.
-Member member = createMember(); -Restaurant restaurant = createRestaurant("restaurant-1", "식당"); -FavoriteRestaurant favoriteRestaurant = new FavoriteRestaurant(member.getId(), restaurant.getId()); +String memberId = "member-1"; +Restaurant restaurant = createRestaurant("restaurant-1", "식당"); +FavoriteRestaurant favoriteRestaurant = new FavoriteRestaurant(memberId, restaurant.getId()); @@ -favoriteService.removeFavoriteRestaurant(member.getId(), restaurant.getId()); +favoriteService.removeFavoriteRestaurant(memberId, restaurant.getId());api-owner/src/test/java/com/wellmeet/global/event/EventPublishServiceTest.java (1)
49-57: 이벤트 퍼블리시 테스트의 memberId null 위험createMember()로 생성한 Member의 id가 null일 수 있어, Reservation 생성 시 null이 들어가도 테스트가 통과합니다. 명시적 memberId(String) 사용으로 보완하세요.
-Member member = createMember(); -Reservation reservation = createReservation(restaurant, availableDate, member); -ReservationConfirmedEvent event = new ReservationConfirmedEvent(reservation, member.getName()); +Member member = createMember(); +String memberId = "member-1"; +Reservation reservation = createReservation(restaurant, availableDate, memberId); +ReservationConfirmedEvent event = new ReservationConfirmedEvent(reservation, member.getName()); @@ -private Reservation createReservation(Restaurant restaurant, AvailableDate availableDate, Member member) { - return new Reservation(restaurant, availableDate, member.getId(), 4, "request"); +private Reservation createReservation(Restaurant restaurant, AvailableDate availableDate, String memberId) { + return new Reservation(restaurant, availableDate, memberId, 4, "request"); }As per coding guidelines
Also applies to: 82-85
🧹 Nitpick comments (18)
domain/build.gradle (1)
15-16: 테스트 픽스처용 JUnit Jupiter 의존성 추가 확인 필요
junit-jupiter-api와junit-jupiter-params가 testFixtures 스코프에 추가되었습니다. 이는 새로운 domain-member 모듈의 파라미터화된 테스트 픽스처를 지원하는 것으로 보이며, 스코프 선택도 적절합니다.다만 다음을 확인해주시기 바랍니다:
- 프로젝트에서 Spring Boot BOM을 사용 중이라면,
junit-jupiter-api와junit-jupiter-params의 버전이 암묵적으로 관리되는지 확인- 기존
spring-boot-starter-test의존성이 testFixtures에 포함되어 있지 않은 경우, 이 추가가 필요한지 재검토api-user/src/test/java/com/wellmeet/restaurant/RestaurantServiceTest.java (1)
176-179: 멤버 객체 생성을 제거하고 ID 문자열을 직접 전달하세요.Member 객체가 생성되지만 getId()를 통해 ID 문자열을 가져오는 용도로만 사용됩니다. Review 생성자가 이제 memberId 문자열을 받으므로, Member 객체 생성 없이 문자열을 직접 전달하여 코드를 단순화할 수 있습니다.
다음 diff를 적용하여 리팩터링하세요:
private Review createReview(Restaurant restaurant) { - Member member = new Member("member", "nickname", "email@test.com", "010-1234-5678"); - return new Review("맛있어요", 4.5, Situation.DATE, restaurant, member.getId()); + return new Review("맛있어요", 4.5, Situation.DATE, restaurant, "member"); }domain-member/src/test/java/com/wellmeet/domain/member/BaseRepositoryTest.java (1)
1-18: 테스트 기반 클래스 구성이 적절합니다.Repository 테스트를 위한 기본 설정이 올바르게 구성되어 있습니다.
@DataJpaTest, JPA Auditing 설정, 그리고DataBaseCleaner를 통한 테스트 격리가 잘 적용되었습니다.단, domain 모듈과 domain-member 모듈 간에 유사한 BaseRepositoryTest가 존재할 가능성이 있습니다. 모듈 분리 맥락에서는 허용 가능하나, 향후 공통 test-fixture 모듈로 통합을 고려해볼 수 있습니다.
domain-member/src/test/java/com/wellmeet/domain/member/DataBaseCleaner.java (1)
38-49: 스키마 이름이 하드코딩되어 있습니다.Line 43에서 스키마 이름
'test'가 하드코딩되어 있어, 테스트 환경 설정에 유연성이 떨어집니다.환경변수나 Spring 프로퍼티를 통해 스키마 이름을 주입받도록 개선할 수 있습니다:
private static final String DEFAULT_SCHEMA = "test"; @SuppressWarnings("unchecked") private List<String> findTableNames(EntityManager em, ApplicationContext context) { String schemaName = context.getEnvironment() .getProperty("spring.datasource.schema", DEFAULT_SCHEMA); String tableNameSelectQuery = """ SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = ? AND TABLE_TYPE = 'BASE TABLE' """; return em.createNativeQuery(tableNameSelectQuery) .setParameter(1, schemaName) .getResultList(); }또는 더 간단하게,
application-domain-test.yml에서 설정된 데이터베이스 연결 정보로부터 스키마를 자동으로 감지할 수도 있습니다.api-user/src/test/java/com/wellmeet/global/event/EventPublishServiceTest.java (1)
91-92: Member 객체 생성을 문자열 리터럴로 대체하는 것을 고려해보세요.현재 코드는 정상 동작하지만, Member 객체를 생성한 후 ID만 추출하는 것보다 memberId를 문자열 리터럴로 직접 사용하는 것이 리팩토링 목표와 더 일관됩니다.
다음과 같이 변경할 수 있습니다:
- Member member = new Member("member", "nickname", "email@test.com", "010-1234-5678"); - return new Reservation(restaurant, availableDate, member.getId(), 4, "요청사항"); + return new Reservation(restaurant, availableDate, "memberId", 4, "요청사항");그리고 Member import도 제거할 수 있습니다:
-import com.wellmeet.domain.member.entity.Member;domain-member/src/testFixtures/java/com/wellmeet/domain/fixture/NullAndEmptyAndBlankSource.java (1)
1-16: 사용 의도 명시와 @documented 추가 제안해당 어노테이션은 단일 String 파라미터 기반의 @ParameterizedTest에 쓰임을 명시하면 오용(다중 파라미터 테스트)에 대한 혼동을 줄일 수 있습니다. @documented도 함께 권장합니다.
package com.wellmeet.domain.fixture; +import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import org.junit.jupiter.params.provider.ArgumentsSource; import org.junit.jupiter.params.provider.NullAndEmptySource; +/** + * 단일 String 파라미터를 받는 @ParameterizedTest에서 + * null, 빈 문자열(""), 공백류(" ", "\t", "\n", "\r\n", "\r") 케이스를 제공합니다. + */ +@Documented @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) @ArgumentsSource(BlankArgumentsProvider.class) @NullAndEmptySource public @interface NullAndEmptyAndBlankSource { }domain/src/test/java/com/wellmeet/domain/reservation/ReservationDomainServiceTest.java (1)
224-227: 테스트 전반에 걸쳐 하드코딩된 문자열 리터럴 사용을 개선할 수 있습니다.테스트에서 "member", "member1", "member2" 같은 문자열 리터럴이 직접 사용되고 있습니다. 테스트 상수를 정의하면 유지보수성과 일관성을 높일 수 있습니다.
다음과 같이 테스트 상수를 추가하는 것을 고려해보세요:
@Import(ReservationDomainService.class) class ReservationDomainServiceTest extends BaseRepositoryTest { + private static final String TEST_MEMBER_ID = "member"; + private static final String TEST_MEMBER_ID_1 = "member1"; + private static final String TEST_MEMBER_ID_2 = "member2"; + @Autowired private ReservationDomainService reservationDomainService;그 후 테스트 전반에서 이 상수들을 사용할 수 있습니다.
api-user/src/test/java/com/wellmeet/restaurant/RestaurantControllerTest.java (1)
23-87: 실패 케이스와 인증/인가 테스트 추가를 권장합니다.코딩 가이드라인에 따르면 컨트롤러 테스트는 "success/failure and auth checks"를 포함해야 합니다. 현재는 성공 케이스만 테스트하고 있습니다.
다음 시나리오들을 추가로 테스트하는 것을 권장합니다:
- 존재하지 않는 레스토랑 ID 조회 시 404 응답
- 유효하지 않은 좌표값 전달 시 적절한 에러 응답
- 인증이 필요한 경우 인증 실패 시나리오
코딩 가이드라인 기준
api-user/src/test/java/com/wellmeet/global/event/listener/ReservationEventListenerTest.java (4)
43-43: 이벤트 인자 의미 명확화 + 상수화하고 페이로드 내용까지 검증하세요리터럴 "member"는 memberId/이름 중 무엇인지 모호합니다. 본 PR 방향(문자열 기반 memberId)과 일치시키려면 상수 MEMBER_ID를 사용하고, Kafka 전송 페이로드에 동일 값이 매핑됐는지 캡처해 검증하는 편이 안전합니다.
적용 예(해당 줄 한정 diff):
- ReservationCreatedEvent event = new ReservationCreatedEvent(reservation, "member"); + ReservationCreatedEvent event = new ReservationCreatedEvent(reservation, MEMBER_ID);추가 제안(클래스 상단/필드):
private static final String MEMBER_ID = "member-123";페이로드 검증(AssertJ 사용):
import static org.assertj.core.api.Assertions.assertThat; import org.mockito.ArgumentCaptor; ArgumentCaptor<ReservationCreatedPayload> captor = ArgumentCaptor.forClass(ReservationCreatedPayload.class); verify(kafkaProducerService).sendNotificationMessage(eq(restaurant.getId()), captor.capture()); assertThat(captor.getValue().getMemberId()).isEqualTo(MEMBER_ID); // getter 명 확인 필요
61-61: Updated 이벤트도 동일한 상수 사용으로 일관성 유지Updated 이벤트에도 MEMBER_ID를 동일하게 전달해 의미/데이터 흐름을 맞추세요. 이후 페이로드 캡처로 값까지 검증하는 것을 권장합니다.
- ReservationUpdatedEvent event = new ReservationUpdatedEvent(reservation, "member"); + ReservationUpdatedEvent event = new ReservationUpdatedEvent(reservation, MEMBER_ID);
79-79: Canceled 이벤트도 상수 재사용하고 페이로드 검증 추가세 테스트 모두 같은 식별자를 사용해야 유지보수성과 의도를 보장합니다. Canceled 페이로드에도 동일 값이 반영되는지 캡처해 단언하세요.
- ReservationCanceledEvent event = new ReservationCanceledEvent(reservation, "member"); + ReservationCanceledEvent event = new ReservationCanceledEvent(reservation, MEMBER_ID);
109-109: Member 의존 제거 및 명시적 memberId 사용 권장(잠재적 null 방지)
new Member(...).getId()는 비영속 객체라 null일 가능성이 큽니다. 모듈 분리 취지상 테스트에서도 Member 엔티티 의존을 끊고, 명시적 문자열memberId를 전달하세요.해당 줄 한정 수정:
- return new Reservation(restaurant, availableDate, member.getId(), 4, "요청사항"); + return new Reservation(restaurant, availableDate, MEMBER_ID, 4, "요청사항");추가로 아래와 같이 Member 생성/임포트를 제거하면 모듈 경계가 더 선명해집니다(참고 스니펫):
// import com.wellmeet.domain.member.entity.Member; // 제거 private Reservation createReservation() { Owner owner = new Owner("owner", "owner@test.com"); Restaurant restaurant = new Restaurant("식당", "description", "서울시 강남구", 37.5, 127.0, "thumbnail.jpg", owner); LocalDateTime dateTime = LocalDateTime.now().plusDays(1); AvailableDate availableDate = new AvailableDate(dateTime.toLocalDate(), dateTime.toLocalTime(), 10, restaurant); return new Reservation(restaurant, availableDate, MEMBER_ID, 4, "요청사항"); }domain-member/src/test/java/com/wellmeet/domain/member/entity/FavoriteRestaurantTest.java (1)
13-22: 생성자 해피패스 테스트 적절함ID 전환 이후 시그니처를 정확히 검증하고 있고, 테스트 스타일도 가이드에 부합합니다. 추가로 null/blank ID 입력 시 방어(예: 예외) 여부도 테스트로 보강하면 회귀에 강해집니다.
As per coding guidelinesapi-user/src/test/java/com/wellmeet/favorite/FavoriteControllerTest.java (3)
42-44: 응답 순서 가정 제거 권장현재 인덱스 0/1에 대한 순서 의존 검증은 저장/정렬 정책에 따라 취약합니다. ID 집합만 검증하도록 변경해 안정성을 높이십시오.
-assertThat(responses[0].getId()).isEqualTo(restaurant1.getId()); -assertThat(responses[1].getId()).isEqualTo(restaurant2.getId()); +assertThat(responses) + .extracting(FavoriteRestaurantResponse::getId) + .containsExactlyInAnyOrder(restaurant1.getId(), restaurant2.getId());As per coding guidelines
30-33: 인증·실패 케이스 테스트 추가컨트롤러 테스트에는 성공뿐 아니라 인증 실패(토큰 미제공/잘못된 토큰), 잘못된 파라미터(누락/존재하지 않는 memberId) 등 부정 케이스도 포함해야 합니다. 별도 테스트 메서드로 보강해 주세요.
As per coding guidelines
76-76: 삭제 플로우 사전조건 OK사전 저장 시에도 ID 기반 생성자가 일관되게 사용되었습니다. 성공 케이스 외에, 미존재 즐겨찾기 삭제 시 404/204 정책에 맞는 응답 검증도 추가해 주세요.
As per coding guidelinesapi-owner/src/test/java/com/wellmeet/reservation/ReservationControllerTest.java (1)
20-42: 부정/권한 케이스 보강 권장
- 잘못된 ownerId 또는 미지정 시 401/403/404 등 정책 검증
- 다른 식당의 예약 포함/제외 규칙 검증
- 응답 필드(예: 예약 인원, 상태, 시간)까지 단언 확대
추가 테스트로 회귀 예방 효과를 높여주세요.
As per coding guidelinesapi-user/src/test/java/com/wellmeet/favorite/FavoriteServiceTest.java (1)
94-101: 추가: AddFavoriteRestaurant 테스트도 명시적 memberId 사용 권장여기도 member.getId()가 null일 수 있습니다. 문자열 memberId로 통일하면 일관성/신뢰성 향상됩니다.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (23)
api-owner/src/test/java/com/wellmeet/global/event/EventPublishServiceTest.java(2 hunks)api-owner/src/test/java/com/wellmeet/global/event/listener/ReservationEventListenerTest.java(2 hunks)api-owner/src/test/java/com/wellmeet/reservation/ReservationControllerTest.java(1 hunks)api-owner/src/test/java/com/wellmeet/reservation/ReservationServiceTest.java(5 hunks)api-user/src/test/java/com/wellmeet/favorite/FavoriteControllerTest.java(2 hunks)api-user/src/test/java/com/wellmeet/favorite/FavoriteServiceTest.java(2 hunks)api-user/src/test/java/com/wellmeet/global/event/EventPublishServiceTest.java(4 hunks)api-user/src/test/java/com/wellmeet/global/event/listener/ReservationEventListenerTest.java(4 hunks)api-user/src/test/java/com/wellmeet/reservation/ReservationControllerTest.java(4 hunks)api-user/src/test/java/com/wellmeet/restaurant/RestaurantControllerTest.java(1 hunks)api-user/src/test/java/com/wellmeet/restaurant/RestaurantServiceTest.java(1 hunks)domain-member/build.gradle(1 hunks)domain-member/src/test/java/com/wellmeet/domain/member/BaseRepositoryTest.java(1 hunks)domain-member/src/test/java/com/wellmeet/domain/member/DataBaseCleaner.java(1 hunks)domain-member/src/test/java/com/wellmeet/domain/member/FavoriteRestaurantDomainServiceTest.java(5 hunks)domain-member/src/test/java/com/wellmeet/domain/member/entity/FavoriteRestaurantTest.java(1 hunks)domain-member/src/testFixtures/java/com/wellmeet/domain/fixture/BlankArgumentsProvider.java(1 hunks)domain-member/src/testFixtures/java/com/wellmeet/domain/fixture/NullAndEmptyAndBlankSource.java(1 hunks)domain/build.gradle(1 hunks)domain/src/test/java/com/wellmeet/domain/reservation/ReservationDomainServiceTest.java(8 hunks)domain/src/test/java/com/wellmeet/domain/reservation/repository/ReservationRepositoryTest.java(6 hunks)domain/src/test/java/com/wellmeet/domain/restaurant/review/ReviewDomainServiceTest.java(4 hunks)domain/src/test/java/com/wellmeet/domain/restaurant/review/repository/ReviewRepositoryTest.java(4 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- domain-member/build.gradle
🧰 Additional context used
📓 Path-based instructions (4)
api-{user,owner}/src/test/java/com/wellmeet/**
📄 CodeRabbit inference engine (CLAUDE.md)
api-{user,owner}/src/test/java/com/wellmeet/**: Controller tests must extend BaseControllerTest, use REST Assured, verify HTTP status and response body, include success/failure and auth checks; no mocks, no @DisplayName or given/when/then comments
Service layer tests may be unit tests with mocks (no base class) or integration tests extending BaseServiceTest; must group with @nested, verify DTO mapping and concurrency using runAtSameTime
Files:
api-owner/src/test/java/com/wellmeet/global/event/listener/ReservationEventListenerTest.javaapi-user/src/test/java/com/wellmeet/reservation/ReservationControllerTest.javaapi-owner/src/test/java/com/wellmeet/reservation/ReservationControllerTest.javaapi-owner/src/test/java/com/wellmeet/global/event/EventPublishServiceTest.javaapi-owner/src/test/java/com/wellmeet/reservation/ReservationServiceTest.javaapi-user/src/test/java/com/wellmeet/favorite/FavoriteServiceTest.javaapi-user/src/test/java/com/wellmeet/global/event/EventPublishServiceTest.javaapi-user/src/test/java/com/wellmeet/restaurant/RestaurantServiceTest.javaapi-user/src/test/java/com/wellmeet/favorite/FavoriteControllerTest.javaapi-user/src/test/java/com/wellmeet/global/event/listener/ReservationEventListenerTest.javaapi-user/src/test/java/com/wellmeet/restaurant/RestaurantControllerTest.java
**/src/test/java/**/*.java
📄 CodeRabbit inference engine (CLAUDE.md)
**/src/test/java/**/*.java: Use AssertJ for assertions across all tests; avoid JUnit assertion styles
Write test method names in Korean with underscores and structure tests using @nested
Do not use @DisplayName in tests
Do not use given/when/then comments; express behavior directly in code
Files:
api-owner/src/test/java/com/wellmeet/global/event/listener/ReservationEventListenerTest.javaapi-user/src/test/java/com/wellmeet/reservation/ReservationControllerTest.javadomain-member/src/test/java/com/wellmeet/domain/member/DataBaseCleaner.javadomain/src/test/java/com/wellmeet/domain/restaurant/review/repository/ReviewRepositoryTest.javadomain/src/test/java/com/wellmeet/domain/reservation/ReservationDomainServiceTest.javadomain/src/test/java/com/wellmeet/domain/reservation/repository/ReservationRepositoryTest.javaapi-owner/src/test/java/com/wellmeet/reservation/ReservationControllerTest.javaapi-owner/src/test/java/com/wellmeet/global/event/EventPublishServiceTest.javadomain-member/src/test/java/com/wellmeet/domain/member/FavoriteRestaurantDomainServiceTest.javaapi-owner/src/test/java/com/wellmeet/reservation/ReservationServiceTest.javaapi-user/src/test/java/com/wellmeet/favorite/FavoriteServiceTest.javaapi-user/src/test/java/com/wellmeet/global/event/EventPublishServiceTest.javadomain-member/src/test/java/com/wellmeet/domain/member/entity/FavoriteRestaurantTest.javaapi-user/src/test/java/com/wellmeet/restaurant/RestaurantServiceTest.javaapi-user/src/test/java/com/wellmeet/favorite/FavoriteControllerTest.javadomain-member/src/test/java/com/wellmeet/domain/member/BaseRepositoryTest.javaapi-user/src/test/java/com/wellmeet/global/event/listener/ReservationEventListenerTest.javaapi-user/src/test/java/com/wellmeet/restaurant/RestaurantControllerTest.javadomain/src/test/java/com/wellmeet/domain/restaurant/review/ReviewDomainServiceTest.java
domain/src/test/java/com/wellmeet/domain/*/*.java
📄 CodeRabbit inference engine (CLAUDE.md)
Domain Service tests should import the service with @import, run with BaseRepositoryTest, validate business logic with repository integration, exceptions, and transaction rollback; group with @nested
Files:
domain/src/test/java/com/wellmeet/domain/reservation/ReservationDomainServiceTest.java
domain/src/test/java/com/wellmeet/domain/*/repository/**/*.java
📄 CodeRabbit inference engine (CLAUDE.md)
domain/src/test/java/com/wellmeet/domain/*/repository/**/*.java: Repository tests must extend BaseRepositoryTest and use @DataJpaTest with real DB (Testcontainers); only test @query custom methods and complex queries
Do not test autogenerated methods (findById, save, findAll) or business logic in Repository tests; group by @nested; no @DisplayName or given/when/then comments
Files:
domain/src/test/java/com/wellmeet/domain/reservation/repository/ReservationRepositoryTest.java
🧠 Learnings (4)
📚 Learning: 2025-10-06T03:23:36.830Z
Learnt from: CR
PR: Team-soft-macaron/WellMeet-Backend#0
File: CLAUDE.md:0-0
Timestamp: 2025-10-06T03:23:36.830Z
Learning: Applies to domain-redis/build.gradle : Include Testcontainers dependencies for Redis tests: org.testcontainers:testcontainers and junit-jupiter 1.19.3
Applied to files:
domain/build.gradle
📚 Learning: 2025-10-06T03:23:36.830Z
Learnt from: CR
PR: Team-soft-macaron/WellMeet-Backend#0
File: CLAUDE.md:0-0
Timestamp: 2025-10-06T03:23:36.830Z
Learning: Applies to domain/src/test/java/com/wellmeet/domain/*/repository/**/*.java : Do not test autogenerated methods (findById, save, findAll) or business logic in Repository tests; group by Nested; no DisplayName or given/when/then comments
Applied to files:
domain/src/test/java/com/wellmeet/domain/restaurant/review/repository/ReviewRepositoryTest.javadomain/src/test/java/com/wellmeet/domain/reservation/repository/ReservationRepositoryTest.javadomain-member/src/test/java/com/wellmeet/domain/member/FavoriteRestaurantDomainServiceTest.javadomain-member/src/test/java/com/wellmeet/domain/member/BaseRepositoryTest.java
📚 Learning: 2025-10-06T03:23:36.830Z
Learnt from: CR
PR: Team-soft-macaron/WellMeet-Backend#0
File: CLAUDE.md:0-0
Timestamp: 2025-10-06T03:23:36.830Z
Learning: Applies to domain/src/test/java/com/wellmeet/domain/*/repository/**/*.java : Repository tests must extend BaseRepositoryTest and use DataJpaTest with real DB (Testcontainers); only test Query custom methods and complex queries
Applied to files:
domain/src/test/java/com/wellmeet/domain/restaurant/review/repository/ReviewRepositoryTest.javadomain/src/test/java/com/wellmeet/domain/reservation/ReservationDomainServiceTest.javadomain/src/test/java/com/wellmeet/domain/reservation/repository/ReservationRepositoryTest.javadomain-member/src/test/java/com/wellmeet/domain/member/FavoriteRestaurantDomainServiceTest.javadomain-member/src/test/java/com/wellmeet/domain/member/BaseRepositoryTest.javadomain/src/test/java/com/wellmeet/domain/restaurant/review/ReviewDomainServiceTest.java
📚 Learning: 2025-10-06T03:23:36.830Z
Learnt from: CR
PR: Team-soft-macaron/WellMeet-Backend#0
File: CLAUDE.md:0-0
Timestamp: 2025-10-06T03:23:36.830Z
Learning: Applies to domain/src/test/java/com/wellmeet/domain/*/*.java : Domain Service tests should import the service with Import, run with BaseRepositoryTest, validate business logic with repository integration, exceptions, and transaction rollback; group with Nested
Applied to files:
domain-member/src/test/java/com/wellmeet/domain/member/FavoriteRestaurantDomainServiceTest.javadomain-member/src/test/java/com/wellmeet/domain/member/BaseRepositoryTest.java
🧬 Code graph analysis (2)
domain-member/src/test/java/com/wellmeet/domain/member/entity/FavoriteRestaurantTest.java (1)
domain-member/src/test/java/com/wellmeet/domain/member/FavoriteRestaurantDomainServiceTest.java (5)
Nested(25-48)Nested(50-74)Nested(76-93)Nested(95-123)Nested(125-143)
domain-member/src/test/java/com/wellmeet/domain/member/BaseRepositoryTest.java (3)
domain-member/src/test/java/com/wellmeet/domain/member/FavoriteRestaurantDomainServiceTest.java (1)
Import(16-149)domain/src/test/java/com/wellmeet/domain/reservation/ReservationDomainServiceTest.java (1)
Import(27-228)domain/src/test/java/com/wellmeet/domain/restaurant/review/ReviewDomainServiceTest.java (1)
Import(20-125)
⏰ 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: build-and-push
🔇 Additional comments (23)
api-user/src/test/java/com/wellmeet/reservation/ReservationControllerTest.java (4)
106-107: 멤버 ID 기반 리팩토링이 올바르게 적용되었습니다.ReservationGenerator 호출이 Member 객체 대신 member.getId()를 전달하도록 정확하게 수정되었으며, 테스트 로직과 검증은 그대로 유지되어 모듈 분리 목표와 일치합니다.
130-130: 멤버 ID 기반 리팩토링이 올바르게 적용되었습니다.ReservationGenerator 호출이 Member 객체 대신 member.getId()를 사용하도록 수정되어 모듈 간 결합도가 감소되었습니다.
153-153: 멤버 ID 기반 리팩토링이 올바르게 적용되었습니다.예약 업데이트 테스트에서도 member.getId()를 사용하도록 일관되게 수정되었습니다.
184-184: 멤버 ID 기반 리팩토링이 올바르게 적용되었습니다.예약 취소 테스트에서도 member.getId()를 사용하도록 일관되게 수정되어, 파일 내 모든 ReservationGenerator 호출이 일관된 패턴으로 업데이트되었습니다.
domain/src/test/java/com/wellmeet/domain/reservation/repository/ReservationRepositoryTest.java (3)
3-3: 좋은 개선입니다.특정 static import를 사용하는 것이 코드의 명확성을 높입니다.
54-56: 멤버 모듈 분리 리팩토링이 올바르게 적용되었습니다.Member 엔티티 참조를 String memberId로 변경한 것은 PR 목표와 일치하며, 리포지토리 테스트에서 간단한 문자열 "member"를 사용하는 것은 쿼리 로직 테스트에 충분합니다.
Also applies to: 83-83, 112-113, 139-139
178-180: 헬퍼 메서드 시그니처 변경이 올바릅니다.Reservation 생성자 호출이 memberId 문자열을 사용하도록 정확하게 업데이트되었으며, 나머지 로직은 적절하게 유지되었습니다.
domain-member/src/test/java/com/wellmeet/domain/member/DataBaseCleaner.java (1)
11-28: 테스트 격리를 위한 데이터베이스 정리 로직이 올바르게 구현되었습니다.
BeforeEachCallback을 통해 각 테스트 전에 데이터베이스를 정리하는 접근 방식이 적절합니다.TransactionTemplate을 사용한 트랜잭션 처리와EntityManager.clear()호출도 올바릅니다.domain/src/test/java/com/wellmeet/domain/restaurant/review/repository/ReviewRepositoryTest.java (2)
36-38: LGTM! 멤버 참조가 문자열 기반으로 깔끔하게 마이그레이션되었습니다.테스트에서 Member 객체 대신 String memberId를 전달하도록 변경이 일관되게 적용되었으며, 테스트 로직과 검증은 그대로 유지되었습니다.
Also applies to: 60-61, 74-75
101-103: LGTM! 헬퍼 메서드 시그니처가 올바르게 업데이트되었습니다.Review 생성자가 이제 memberId 문자열을 받도록 변경되었으며, 헬퍼 메서드도 이에 맞춰 일관되게 수정되었습니다.
domain/src/test/java/com/wellmeet/domain/restaurant/review/ReviewDomainServiceTest.java (2)
43-44: LGTM! Repository 테스트와 동일한 패턴으로 일관되게 리팩토링되었습니다.도메인 서비스 테스트에서도 Member 객체 대신 String memberId를 사용하도록 변경이 올바르게 적용되었습니다.
Also applies to: 70-71, 94-95
121-123: LGTM! 헬퍼 메서드가 올바르게 업데이트되었습니다.Review 엔티티의 생성자 변경에 맞춰 헬퍼 메서드가 일관되게 수정되었습니다.
api-user/src/test/java/com/wellmeet/global/event/EventPublishServiceTest.java (1)
37-37: LGTM! 이벤트 생성자가 올바르게 업데이트되었습니다.모든 예약 이벤트가 이제 (reservation, memberName) 시그니처를 사용하도록 일관되게 변경되었습니다.
Also applies to: 51-51, 65-65
domain-member/src/testFixtures/java/com/wellmeet/domain/fixture/NullAndEmptyAndBlankSource.java (1)
10-14: 구성형 파라미터 소스 정의 적절합니다METHOD 타겟, RUNTIME 보존, JUnit5 메타어노테이션 조합이 올바릅니다.
domain/src/test/java/com/wellmeet/domain/reservation/ReservationDomainServiceTest.java (2)
82-94: 권한 검증 테스트 로직이 잘 구현되었습니다.서로 다른 memberId("member1", "member2")를 사용하여 다른 회원의 예약 조회 시 예외가 발생하는지 검증하는 테스트가 올바르게 작성되었습니다.
54-54: 리뷰 의견 검증 완료: 멤버 ID 검증이 이미 구현되어 있습니다.애플리케이션 레이어(ReservationService)에서
memberDomainService.getById(memberId)를 호출하여 예약 생성 전에 회원 존재 여부를 검증합니다. 도메인 테스트에서 String memberId를 사용하는 것은 도메인 레이어를 독립적으로 테스트하는 올바른 접근입니다. 참조 무결성은 애플리케이션 레이어에서 보장됩니다.Likely an incorrect or invalid review comment.
api-user/src/test/java/com/wellmeet/restaurant/RestaurantControllerTest.java (1)
53-54: 라인 53-54의 코드 변경이 정확히 적용되었습니다.Member 엔티티의
id필드가String타입이므로,member.getId()는String을 반환합니다. ReviewGenerator의generate()메서드 시그니처generate(int rating, Restaurant restaurant, String memberId)와 정확히 일치합니다. Member 객체 대신 member.getId()를 전달하도록 변경한 것은 member 모듈 분리 리팩토링에 부합하며, 타입 안정성도 유지되고 있습니다.api-owner/src/test/java/com/wellmeet/reservation/ReservationServiceTest.java (3)
33-34: MemberDomainService 목(mock) 추가가 적절합니다.리팩토링된 ReservationService가 회원 정보를 조회하기 위해 MemberDomainService에 의존하므로 목 객체 추가가 필요합니다.
105-105: Reservation 생성자 시그니처 변경이 리팩토링 목표와 일치합니다.Member 객체 대신
member.getId()를 전달하도록 변경한 것이 모듈 분리 목표에 부합합니다. 다만, 위에서 지적한 Member ID null 문제가 해결되어야 정상 동작합니다.
57-60: 지정된 파일과 코드를 직접 검토할 수 없으므로, 이 검증 과정을 완료하기 위해 코드베이스 상태를 수동으로 확인해야 합니다. 웹 검색 결과와 일반 지식을 토대로, 제시된 검토 의견은 유효한 관심사를 지적하고 있습니다:ID가 데이터베이스에 의해 설정되고 저장소가 Mockito로 모킹되면, 이 값은 항상 null입니다. 이는
createMember()헬퍼가 Member 객체를 생성할 때 ID를 명시적으로 설정하지 않으면,member.getId()가 null을 반환할 수 있음을 의미합니다.Member ID를 명시적으로 설정하거나 테스트 설정을 수정하세요.
createMember()헬퍼 메서드에서 다음 중 하나를 수행해야 합니다:
- Member 객체 생성 후 ID를 명시적으로 설정하거나
- ReflectionTestUtils.setField()를 사용하여 테스트 설정 중에 ID를 수동으로 할당하거나
when(memberDomainService.getById(any(Long.class))).thenReturn(member)형태로 mock 설정을 변경현재 상태에서
member.getId()가 null을 반환하면,when(memberDomainService.getById(null))로 모킹되어 테스트가 의도대로 동작하지 않습니다.api-user/src/test/java/com/wellmeet/favorite/FavoriteControllerTest.java (1)
30-33: ID 기반 생성자 사용으로 정합성 확보Member/Restaurant 참조 → ID(String)로 저장 방식 변경이 컨트롤러 플로우와 일치합니다. 이 변경 자체는 적절합니다.
As per coding guidelinesapi-owner/src/test/java/com/wellmeet/reservation/ReservationControllerTest.java (1)
30-32: 멤버 참조 → ID(String) 전환 반영 OKreservationGenerator가 memberId(String)를 받도록 변경된 점을 정확히 적용했습니다.
domain-member/src/test/java/com/wellmeet/domain/member/FavoriteRestaurantDomainServiceTest.java (1)
29-142: ID 기반 API로의 마이그레이션이 일관되게 적용되었습니다.엔티티 참조에서 ID 기반 접근으로의 리팩토링이 모든 테스트 케이스에 정확하게 반영되었으며, 테스트 명명 규칙과 구조가 코딩 가이드라인을 올바르게 따르고 있습니다.
| Member member = createMember(); | ||
| Restaurant restaurant1 = createRestaurant("restaurant-1", "식당1"); | ||
| Restaurant restaurant2 = createRestaurant("restaurant-2", "식당2"); | ||
| FavoriteRestaurant favorite1 = new FavoriteRestaurant(member, restaurant1); | ||
| FavoriteRestaurant favorite2 = new FavoriteRestaurant(member, restaurant2); | ||
| FavoriteRestaurant favorite1 = new FavoriteRestaurant(member.getId(), restaurant1.getId()); | ||
| FavoriteRestaurant favorite2 = new FavoriteRestaurant(member.getId(), restaurant2.getId()); | ||
| List<FavoriteRestaurant> favorites = List.of(favorite1, favorite2); | ||
|
|
||
| when(favoriteRestaurantDomainService.findAllByMemberId(member.getId())) | ||
| .thenReturn(favorites); | ||
| when(reviewDomainService.getAverageRating("restaurant-1")).thenReturn(4.5); | ||
| when(reviewDomainService.getAverageRating("restaurant-2")).thenReturn(3.8); | ||
| when(restaurantDomainService.getById(restaurant1.getId())).thenReturn(restaurant1); | ||
| when(restaurantDomainService.getById(restaurant2.getId())).thenReturn(restaurant2); | ||
|
|
||
| List<FavoriteRestaurantResponse> result = favoriteService.getFavoriteRestaurants(member.getId()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
member.getId()가 null일 수 있어 테스트가 허위 성공할 수 있음
영속 전 Member의 id는 null일 가능성이 높습니다. 현재 스텁/검증에서 동일한 null을 사용하면 테스트가 의미를 잃습니다. 명시적 memberId 문자열 사용으로 수정하세요.
-Member member = createMember();
-Restaurant restaurant1 = createRestaurant("restaurant-1", "식당1");
-Restaurant restaurant2 = createRestaurant("restaurant-2", "식당2");
-FavoriteRestaurant favorite1 = new FavoriteRestaurant(member.getId(), restaurant1.getId());
-FavoriteRestaurant favorite2 = new FavoriteRestaurant(member.getId(), restaurant2.getId());
-List<FavoriteRestaurant> favorites = List.of(favorite1, favorite2);
-when(favoriteRestaurantDomainService.findAllByMemberId(member.getId()))
+String memberId = "member-1";
+Restaurant restaurant1 = createRestaurant("restaurant-1", "식당1");
+Restaurant restaurant2 = createRestaurant("restaurant-2", "식당2");
+FavoriteRestaurant favorite1 = new FavoriteRestaurant(memberId, restaurant1.getId());
+FavoriteRestaurant favorite2 = new FavoriteRestaurant(memberId, restaurant2.getId());
+List<FavoriteRestaurant> favorites = List.of(favorite1, favorite2);
+when(favoriteRestaurantDomainService.findAllByMemberId(memberId))
.thenReturn(favorites);
when(reviewDomainService.getAverageRating("restaurant-1")).thenReturn(4.5);
when(reviewDomainService.getAverageRating("restaurant-2")).thenReturn(3.8);
-when(restaurantDomainService.getById(restaurant1.getId())).thenReturn(restaurant1);
-when(restaurantDomainService.getById(restaurant2.getId())).thenReturn(restaurant2);
+when(restaurantDomainService.getById(restaurant1.getId())).thenReturn(restaurant1);
+when(restaurantDomainService.getById(restaurant2.getId())).thenReturn(restaurant2);
-
-List<FavoriteRestaurantResponse> result = favoriteService.getFavoriteRestaurants(member.getId());
+List<FavoriteRestaurantResponse> result = favoriteService.getFavoriteRestaurants(memberId);As per coding guidelines
🤖 Prompt for AI Agents
In api-user/src/test/java/com/wellmeet/favorite/FavoriteServiceTest.java around
lines 45 to 59, the test relies on member.getId() which may be null and cause a
false positive; replace use of member.getId() with an explicit non-null memberId
string (e.g., "member-1"), create FavoriteRestaurant instances and stubs using
that memberId, ensure restaurant stubs use their explicit ids, and call
favoriteService.getFavoriteRestaurants(memberId) instead of passing
member.getId().
| private void truncateTables(EntityManager em) { | ||
| em.createNativeQuery("SET FOREIGN_KEY_CHECKS = 0").executeUpdate(); | ||
| for (String tableName : findTableNames(em)) { | ||
| em.createNativeQuery("TRUNCATE TABLE %s".formatted(tableName)).executeUpdate(); | ||
| } | ||
| em.createNativeQuery("SET FOREIGN_KEY_CHECKS = 1").executeUpdate(); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
MySQL 전용 문법으로 인한 데이터베이스 이식성 제한
SET FOREIGN_KEY_CHECKS는 MySQL/MariaDB 전용 문법입니다. PostgreSQL, H2 등 다른 데이터베이스에서는 동작하지 않습니다.
현재 프로젝트가 MySQL만 사용한다면 문제없지만, 향후 다른 데이터베이스 지원이나 H2 기반 경량 테스트로의 전환을 고려한다면 이식성 문제가 발생할 수 있습니다.
만약 데이터베이스 이식성이 필요하다면, 다음과 같은 대안을 고려할 수 있습니다:
- JPA를 통한 cascade delete 활용
- 데이터베이스별 분기 처리
- Flyway/Liquibase를 통한 스키마 재생성
private void truncateTables(EntityManager em) {
// MySQL/MariaDB
em.createNativeQuery("SET FOREIGN_KEY_CHECKS = 0").executeUpdate();
for (String tableName : findTableNames(em)) {
em.createNativeQuery("TRUNCATE TABLE %s".formatted(tableName)).executeUpdate();
}
em.createNativeQuery("SET FOREIGN_KEY_CHECKS = 1").executeUpdate();
// PostgreSQL alternative (if needed):
// ALTER TABLE ... DISABLE TRIGGER ALL
// TRUNCATE TABLE ... CASCADE
// ALTER TABLE ... ENABLE TRIGGER ALL
}🤖 Prompt for AI Agents
In domain-member/src/test/java/com/wellmeet/domain/member/DataBaseCleaner.java
around lines 30 to 36, the current truncation uses MySQL-specific "SET
FOREIGN_KEY_CHECKS" which breaks portability; change the cleanup to a portable
approach: either (1) replace native truncates with JPA bulk deletes
(EntityManager.createQuery("DELETE FROM EntityName").executeUpdate()) for each
mapped entity inside a transaction so foreign keys are respected, or (2) detect
the dialect at runtime and branch to database-specific statements (MySQL: SET
FOREIGN_KEY_CHECKS; PostgreSQL: TRUNCATE ... CASCADE or ALTER TABLE ... DISABLE
TRIGGER ALL/ENABLE TRIGGER ALL) before/after truncation, or (3) run schema reset
via test migration tool (Flyway/Liquibase) where available; implement the chosen
approach, remove the hardcoded MySQL-only calls, and ensure the cleanup works
for H2/Postgres by adding tests or conditional logic to pick the correct
strategy.
domain-member/src/test/java/com/wellmeet/domain/member/FavoriteRestaurantDomainServiceTest.java
Show resolved
Hide resolved
| @Override | ||
| public Stream<? extends Arguments> provideArguments(ExtensionContext context) throws Exception { | ||
| return Stream.of(Arguments.arguments(" ", "\t", "\n", "\r\n", "\r")); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
단일 호출에 5개 인자를 전달하여 파라미터 개수 불일치 발생
현재 구현은 Arguments 인스턴스 1개(값 5개)를 반환해, 단일 파라미터 테스트에서 JUnit 인자 개수 불일치로 실패합니다. 각 공백 값을 개별 Arguments로 매핑해야 합니다.
수정안:
- return Stream.of(Arguments.arguments(" ", "\t", "\n", "\r\n", "\r"));
+ return Stream.of(" ", "\t", "\n", "\r\n", "\r")
+ .map(Arguments::of);선택 개선:
- 필요 시 다양한 공백 문자 확장(예: "\u00A0" NBSP, "\u2003" EM SPACE, "\u2009" THIN SPACE, "\u200B" ZERO WIDTH SPACE 등)도 고려해 보세요.
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| @Override | |
| public Stream<? extends Arguments> provideArguments(ExtensionContext context) throws Exception { | |
| return Stream.of(Arguments.arguments(" ", "\t", "\n", "\r\n", "\r")); | |
| } | |
| @Override | |
| public Stream<? extends Arguments> provideArguments(ExtensionContext context) throws Exception { | |
| return Stream.of(" ", "\t", "\n", "\r\n", "\r") | |
| .map(Arguments::of); | |
| } |
🤖 Prompt for AI Agents
In
domain-member/src/testFixtures/java/com/wellmeet/domain/fixture/BlankArgumentsProvider.java
around lines 10 to 13, the method currently returns a single Arguments
containing five values which causes parameter count mismatch in single-parameter
JUnit tests; change it to return a Stream of five separate Arguments instances
(one per blank value) so each test invocation receives exactly one parameter;
optionally extend the stream with additional whitespace Unicode variants if
broader coverage is desired.
There was a problem hiding this 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 (2)
domain-member/src/test/java/com/wellmeet/domain/member/TestConfiguration.java (1)
5-7: 테스트 설정 구조는 적절합니다.
@SpringBootApplication을 사용한 빈 설정 클래스는 테스트 컨텍스트 부트스트랩을 위한 표준 패턴입니다. 컴포넌트 스캔과 자동 구성이 적절히 활성화됩니다.다음 개선사항을 고려해보세요:
- 이 설정 클래스의 목적을 설명하는 간단한 JavaDoc 추가 (예: 도메인 멤버 모듈의 테스트 컨텍스트 설정)
- 필요한 경우 컴포넌트 스캔 범위가 의도한 대로인지 확인
/** * domain-member 모듈의 통합 테스트를 위한 Spring Boot 테스트 설정. * 컴포넌트 스캔 및 자동 구성을 활성화합니다. */ @SpringBootApplication public class TestConfiguration { }domain-member/src/test/java/com/wellmeet/domain/member/FavoriteRestaurantDomainServiceTest.java (1)
28-146: 트랜잭션 롤백 동작 검증 추가를 고려하세요.학습된 가이드라인에 따르면 도메인 서비스 테스트는 트랜잭션 롤백 동작도 검증해야 합니다. 현재 테스트는 비즈니스 로직과 예외 처리를 잘 검증하고 있으나, 트랜잭션 경계에서의 롤백 동작 테스트가 없습니다.
예를 들어, 예외 발생 시 데이터가 롤백되는지 확인하는 테스트를 추가할 수 있습니다.
Based on learnings
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (3)
domain-member/src/test/java/com/wellmeet/domain/member/FavoriteRestaurantDomainServiceTest.java(5 hunks)domain-member/src/test/java/com/wellmeet/domain/member/MemberDomainServiceTest.java(0 hunks)domain-member/src/test/java/com/wellmeet/domain/member/TestConfiguration.java(1 hunks)
💤 Files with no reviewable changes (1)
- domain-member/src/test/java/com/wellmeet/domain/member/MemberDomainServiceTest.java
🧰 Additional context used
📓 Path-based instructions (1)
**/src/test/java/**/*.java
📄 CodeRabbit inference engine (CLAUDE.md)
**/src/test/java/**/*.java: Use AssertJ for assertions across all tests; avoid JUnit assertion styles
Write test method names in Korean with underscores and structure tests using @nested
Do not use @DisplayName in tests
Do not use given/when/then comments; express behavior directly in code
Files:
domain-member/src/test/java/com/wellmeet/domain/member/FavoriteRestaurantDomainServiceTest.javadomain-member/src/test/java/com/wellmeet/domain/member/TestConfiguration.java
🧠 Learnings (3)
📚 Learning: 2025-10-06T03:23:36.830Z
Learnt from: CR
PR: Team-soft-macaron/WellMeet-Backend#0
File: CLAUDE.md:0-0
Timestamp: 2025-10-06T03:23:36.830Z
Learning: Applies to domain/src/test/java/com/wellmeet/domain/*/repository/**/*.java : Repository tests must extend BaseRepositoryTest and use DataJpaTest with real DB (Testcontainers); only test Query custom methods and complex queries
Applied to files:
domain-member/src/test/java/com/wellmeet/domain/member/FavoriteRestaurantDomainServiceTest.java
📚 Learning: 2025-10-06T03:23:36.830Z
Learnt from: CR
PR: Team-soft-macaron/WellMeet-Backend#0
File: CLAUDE.md:0-0
Timestamp: 2025-10-06T03:23:36.830Z
Learning: Applies to domain/src/test/java/com/wellmeet/domain/*/*.java : Domain Service tests should import the service with Import, run with BaseRepositoryTest, validate business logic with repository integration, exceptions, and transaction rollback; group with Nested
Applied to files:
domain-member/src/test/java/com/wellmeet/domain/member/FavoriteRestaurantDomainServiceTest.java
📚 Learning: 2025-10-06T03:23:36.830Z
Learnt from: CR
PR: Team-soft-macaron/WellMeet-Backend#0
File: CLAUDE.md:0-0
Timestamp: 2025-10-06T03:23:36.830Z
Learning: Applies to domain/src/test/java/com/wellmeet/domain/*/repository/**/*.java : Do not test autogenerated methods (findById, save, findAll) or business logic in Repository tests; group by Nested; no DisplayName or given/when/then comments
Applied to files:
domain-member/src/test/java/com/wellmeet/domain/member/FavoriteRestaurantDomainServiceTest.java
🔇 Additional comments (3)
domain-member/src/test/java/com/wellmeet/domain/member/FavoriteRestaurantDomainServiceTest.java (3)
19-20: 이전 리뷰 코멘트가 반영되었습니다.서비스 필드 선언이 추가되어 컴파일 에러가 해결되었습니다.
1-152: 코딩 가이드라인을 잘 준수한 테스트 구조입니다.
- AssertJ를 사용한 어설션
- 한글 언더스코어 메서드명
@DisplayName미사용- given/when/then 주석 없음
@Nested를 활용한 메서드별 그룹핑테스트 구조가 프로젝트 가이드라인을 모두 따르고 있습니다.
As per coding guidelines
35-35: 타입 호환성 검증 완료: 우려사항이 근거가 없습니다.코드베이스를 검증한 결과,
Member.getId()는String을 반환하고,FavoriteRestaurant생성자는(String memberId, String restaurantId)를 기대하며,getMemberId()도String을 반환합니다. 모든 타입이 일치하므로 컴파일 에러나 어설션 실패는 발생하지 않습니다.Likely an incorrect or invalid review comment.
|
@gemini review |
|
/gemini review |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Code Review
member 도메인을 domain-member 모듈로 성공적으로 분리하신 점이 인상적입니다. 도메인 경계를 넘어 ID 기반으로 참조하도록 변경한 것은 모듈식 아키텍처를 향한 훌륭한 리팩터링입니다. 다만, 이 과정에서 member와 다른 도메인의 데이터를 조합해야 하는 서비스 로직에 N+1 쿼리 문제가 여러 곳에서 발생했습니다. 특히 배치 프로세스에서의 성능 문제는 중요하게 다루어야 할 부분입니다. 또한, domain과 domain-member 모듈 간에 일부 코드가 중복되고 있어, 공통 모듈을 만들어 해결하는 것을 고려해볼 수 있습니다.
| return reservationDomainService.findAllByRestaurantId(restaurantId) | ||
| .stream() | ||
| .map(ReservationResponse::new) | ||
| .map(reservation -> { | ||
| Member member = memberDomainService.getById(reservation.getMemberId()); | ||
| return new ReservationResponse( | ||
| reservation, | ||
| member.getName(), | ||
| member.getPhone(), | ||
| member.getEmail(), | ||
| member.isVip() | ||
| ); | ||
| }) | ||
| .toList(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
getReservations 메소드에서 N+1 쿼리 문제가 발생할 수 있습니다. findAllByRestaurantId로 가져온 예약 목록을 순회하면서 각 예약에 대해 memberDomainService.getById()를 호출하고 있습니다. 예약 건수가 많아질 경우, 데이터베이스에 대한 개별 쿼리가 예약 건수만큼 발생하여 성능 저하를 유발할 수 있습니다.
이 문제를 해결하기 위해, 모든 예약에 필요한 memberId를 한 번에 수집한 후, memberDomainService에 findAllByIds와 같은 메소드를 추가하여 한 번의 쿼리로 모든 회원 정보를 가져오는 것을 권장합니다. 가져온 회원 정보는 Map<String, Member> 형태로 만들어두고 예약 목록을 처리할 때 사용하면 효율적입니다.
예시:
@Transactional(readOnly = true)
public List<ReservationResponse> getReservations(String restaurantId) {
List<Reservation> reservations = reservationDomainService.findAllByRestaurantId(restaurantId);
if (reservations.isEmpty()) {
return Collections.emptyList();
}
List<String> memberIds = reservations.stream()
.map(Reservation::getMemberId)
.distinct()
.toList();
Map<String, Member> membersById = memberDomainService.findAllByIds(memberIds).stream()
.collect(Collectors.toMap(Member::getId, Function.identity()));
return reservations.stream()
.map(reservation -> {
Member member = membersById.get(reservation.getMemberId());
// member가 null일 경우에 대한 처리도 고려해야 합니다.
return new ReservationResponse(
reservation,
member.getName(),
member.getPhone(),
member.getEmail(),
member.isVip()
);
})
.toList();
}(참고: memberDomainService.findAllByIds는 새로 추가해야 하는 메소드입니다.)
| public List<FavoriteRestaurantResponse> getFavoriteRestaurants(String memberId) { | ||
| return favoriteRestaurantDomainService.findAllByMemberId(memberId) | ||
| .stream() | ||
| .map(favoriteRestaurant -> getFavoriteRestaurantResponse(favoriteRestaurant.getRestaurant())) | ||
| .map(favoriteRestaurant -> { | ||
| Restaurant restaurant = restaurantDomainService.getById(favoriteRestaurant.getRestaurantId()); | ||
| return getFavoriteRestaurantResponse(restaurant); | ||
| }) | ||
| .toList(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
getFavoriteRestaurants 메소드에서 N+1 쿼리 문제가 발생할 수 있습니다. findAllByMemberId로 즐겨찾기 목록을 가져온 후, 각 항목에 대해 restaurantDomainService.getById()를 호출하여 레스토랑 정보를 개별적으로 조회하고 있습니다. 즐겨찾기한 레스토랑이 많아지면 성능에 영향을 줄 수 있습니다.
ReservationService에서 제안한 것과 유사하게, 모든 restaurantId를 수집하여 한 번의 쿼리로 모든 레스토랑 정보를 가져오는 방식으로 개선하는 것이 좋습니다.
예시:
@Transactional(readOnly = true)
public List<FavoriteRestaurantResponse> getFavoriteRestaurants(String memberId) {
List<FavoriteRestaurant> favoriteRestaurants = favoriteRestaurantDomainService.findAllByMemberId(memberId);
if (favoriteRestaurants.isEmpty()) {
return Collections.emptyList();
}
List<String> restaurantIds = favoriteRestaurants.stream()
.map(FavoriteRestaurant::getRestaurantId)
.toList();
Map<String, Restaurant> restaurantsById = restaurantDomainService.findAllByIds(restaurantIds).stream()
.collect(Collectors.toMap(Restaurant::getId, Function.identity()));
return favoriteRestaurants.stream()
.map(favoriteRestaurant -> {
Restaurant restaurant = restaurantsById.get(favoriteRestaurant.getRestaurantId());
// restaurant가 null일 경우에 대한 처리도 고려해야 합니다.
return getFavoriteRestaurantResponse(restaurant);
})
.toList();
}(참고: restaurantDomainService.findAllByIds는 새로 추가해야 하는 메소드입니다.)
| package com.wellmeet.domain.common; | ||
|
|
||
| import jakarta.persistence.EntityListeners; | ||
| import jakarta.persistence.MappedSuperclass; | ||
| import jakarta.validation.constraints.NotNull; | ||
| import java.time.LocalDateTime; | ||
| import lombok.Getter; | ||
| import org.springframework.data.annotation.CreatedDate; | ||
| import org.springframework.data.annotation.LastModifiedDate; | ||
| import org.springframework.data.jpa.domain.support.AuditingEntityListener; | ||
|
|
||
| @Getter | ||
| @EntityListeners(AuditingEntityListener.class) | ||
| @MappedSuperclass | ||
| public abstract class BaseEntity { | ||
|
|
||
| @NotNull | ||
| @CreatedDate | ||
| private LocalDateTime createdAt; | ||
|
|
||
| @NotNull | ||
| @LastModifiedDate | ||
| private LocalDateTime updatedAt; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
|
/gemini review |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Code Review
이번 PR은 member 도메인을 별도의 모듈로 분리하는 중요한 리팩토링을 수행했습니다. 도메인 간의 경계를 명확히 하고 의존성을 줄이는 좋은 방향의 변경입니다. 전반적으로 변경 사항은 훌륭하지만, 몇 가지 잠재적인 문제점과 개선 사항을 발견했습니다. 특히 N+1 쿼리 문제와 NullPointerException 발생 가능성에 대한 검토가 필요해 보입니다. 자세한 내용은 각 파일의 주석을 참고해주세요.
| Member member = membersById.get(reservation.getMemberId()); | ||
| return new ReservationResponse( | ||
| reservation, | ||
| member.getName(), | ||
| member.getPhone(), | ||
| member.getEmail(), | ||
| member.isVip() | ||
| ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
membersById.get(reservation.getMemberId()) 호출 시, 만약 memberIds에 해당하는 Member가 존재하지 않으면 (예: 회원 탈퇴로 인해 데이터 정합성이 깨진 경우) null이 반환될 수 있습니다. 이 경우 member.getName()을 호출할 때 NullPointerException이 발생하여 API 전체가 실패할 수 있습니다. 데이터가 항상 존재한다고 보장할 수 없다면, null 체크를 추가하여 예외 처리를 하는 것이 안전합니다.
Member member = membersById.get(reservation.getMemberId());
if (member == null) {
throw new IllegalStateException("ID에 해당하는 회원을 찾을 수 없습니다: " + reservation.getMemberId());
}
return new ReservationResponse(
reservation,
member.getName(),
member.getPhone(),
member.getEmail(),
member.isVip()
);| Restaurant restaurant = restaurantsById.get(favoriteRestaurant.getRestaurantId()); | ||
| return getFavoriteRestaurantResponse(restaurant); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
restaurantsById.get(favoriteRestaurant.getRestaurantId()) 호출 시, 만약 restaurantIds에 해당하는 Restaurant가 존재하지 않으면(예: 식당 정보 삭제) null이 반환될 수 있습니다. 이 null 값이 getFavoriteRestaurantResponse 메소드로 전달되면 NullPointerException이 발생할 수 있습니다. null 체크를 추가하여 데이터 불일치 상황에 대비하는 것이 좋습니다.
Restaurant restaurant = restaurantsById.get(favoriteRestaurant.getRestaurantId());
if (restaurant == null) {
throw new IllegalStateException("ID에 해당하는 식당을 찾을 수 없습니다: " + favoriteRestaurant.getRestaurantId());
}
return getFavoriteRestaurantResponse(restaurant);| public ReservationReminderPayload process(Reservation reservation) { | ||
| log.info("Processing reservation reminder for reservation ID: {}", reservation.getId()); | ||
|
|
||
| Member member = memberDomainService.getById(reservation.getMemberId()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ItemProcessor의 process 메소드 내에서 각 예약 건마다 memberDomainService.getById()를 호출하고 있습니다. 이는 전형적인 N+1 쿼리 문제로, 처리해야 할 예약 건수가 많아질 경우 배치 작업의 성능을 심각하게 저하시킬 수 있습니다. 회원 정보를 한 번에 대량으로 가져오는 방식으로 최적화하는 것을 고려해야 합니다. 예를 들어, Spring Batch의 ChunkListener를 사용하여 청크 단위로 필요한 회원 데이터를 미리 로드하거나, 페이징 처리된 예약 목록에 대해 필요한 회원 정보를 미리 조회하여 메모리에 올려두고 사용하는 커스텀 ItemReader를 구현하는 방법이 있습니다.
| when(memberDomainService.getById(member1.getId())) | ||
| .thenReturn(member1); | ||
| when(memberDomainService.getById(member2.getId())) | ||
| .thenReturn(member2); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| when(restaurantDomainService.getById(restaurant1.getId())).thenReturn(restaurant1); | ||
| when(restaurantDomainService.getById(restaurant2.getId())).thenReturn(restaurant2); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
테스트에서 restaurantDomainService.getById()를 모킹하고 있지만, 실제 getFavoriteRestaurants 메소드 구현에서는 restaurantDomainService.findAllByIds()를 사용하고 있습니다. 테스트가 실제 코드의 동작을 올바르게 검증하도록 findAllByIds()를 모킹하는 것으로 수정해야 합니다.
| when(restaurantDomainService.getById(restaurant1.getId())).thenReturn(restaurant1); | |
| when(restaurantDomainService.getById(restaurant2.getId())).thenReturn(restaurant2); | |
| when(restaurantDomainService.findAllByIds(any())).thenReturn(List.of(restaurant1, restaurant2)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
♻️ Duplicate comments (1)
api-user/src/main/java/com/wellmeet/favorite/FavoriteService.java (1)
27-44: N+1 쿼리 문제를 해결했지만 null 처리가 필요합니다.과거 리뷰에서 지적된 N+1 쿼리 문제를 배치 로딩으로 올바르게 해결했습니다. 하지만 Line 41에서
restaurantsById.get()으로 가져온restaurant가 null일 수 있는 경우를 처리하지 않고 있습니다. 레스토랑이 삭제되었지만 즐겨찾기에는 남아있는 경우 NPE가 발생할 수 있습니다.다음과 같이 null 체크를 추가하세요:
return favoriteRestaurants.stream() .map(favoriteRestaurant -> { Restaurant restaurant = restaurantsById.get(favoriteRestaurant.getRestaurantId()); + if (restaurant == null) { + // 로깅 후 스킵하거나, 또는 예외를 던지는 등의 처리 필요 + return null; + } return getFavoriteRestaurantResponse(restaurant); }) + .filter(response -> response != null) .toList();또는 존재하지 않는 레스토랑에 대한 즐겨찾기를 정리하는 로직을 추가하는 것도 고려해보세요.
🧹 Nitpick comments (3)
domain-member/src/main/java/com/wellmeet/domain/member/MemberDomainService.java (2)
7-7: 사용되지 않는 import를 제거하는 것을 고려하세요.제공된 코드에서
Collectionimport가 사용되지 않는 것으로 보입니다. 파일의 다른 부분에서 사용되지 않는다면 제거하는 것이 좋습니다.
23-25: 배치 조회 메서드가 올바르게 구현되었습니다.Repository로의 위임이 명확하며, N+1 쿼리 문제 해결을 위한 배치 조회 기능을 제공합니다.
선택적 개선 사항:
memberIds파라미터가 null인 경우를 방어적으로 처리하는 것을 고려할 수 있지만, 기존getById메서드와 일관된 패턴을 따르고 있습니다.domain/src/main/java/com/wellmeet/domain/restaurant/RestaurantDomainService.java (1)
16-16: 사용되지 않는 import를 제거하는 것을 고려하세요.제공된 코드에서
Collectionimport가 사용되지 않는 것으로 보입니다. 파일의 다른 부분에서 사용되지 않는다면 제거하는 것이 좋습니다.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (6)
api-owner/src/main/java/com/wellmeet/reservation/ReservationService.java(3 hunks)api-user/src/main/java/com/wellmeet/favorite/FavoriteService.java(3 hunks)domain-member/src/main/java/com/wellmeet/domain/member/MemberDomainService.java(2 hunks)domain-member/src/main/java/com/wellmeet/domain/member/repository/MemberRepository.java(1 hunks)domain/src/main/java/com/wellmeet/domain/restaurant/RestaurantDomainService.java(2 hunks)domain/src/main/java/com/wellmeet/domain/restaurant/repository/RestaurantRepository.java(1 hunks)
⏰ 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: build-and-push
🔇 Additional comments (6)
api-user/src/main/java/com/wellmeet/favorite/FavoriteService.java (1)
55-55: 멤버 모듈 분리에 맞춰 올바르게 리팩토링되었습니다.
FavoriteRestaurant생성자를 엔티티 참조에서 ID 기반으로 변경하여 모듈 간 결합도를 낮췄습니다. PR의 목표와 일치하는 좋은 변경입니다.domain/src/main/java/com/wellmeet/domain/restaurant/repository/RestaurantRepository.java (1)
21-22: 배치 조회 메서드 추가를 승인합니다.Spring Data JPA의 파생 쿼리 메서드 패턴을 올바르게 따르고 있으며, 여러 레스토랑을 한 번에 조회할 수 있는 기능을 제공합니다.
domain-member/src/main/java/com/wellmeet/domain/member/repository/MemberRepository.java (1)
10-11: 배치 조회 메서드 추가를 승인합니다.Spring Data JPA 파생 쿼리 규칙을 올바르게 따르며, N+1 쿼리 문제를 해결하기 위한 배치 조회 기능을 제공합니다.
domain/src/main/java/com/wellmeet/domain/restaurant/RestaurantDomainService.java (1)
76-78: 배치 조회 메서드가 올바르게 구현되었습니다.Repository로의 위임이 명확하며, 일관된 패턴을 따릅니다. MemberDomainService의 동일한 메서드와 일관성을 유지합니다.
api-owner/src/main/java/com/wellmeet/reservation/ReservationService.java (2)
28-38: N+1 쿼리 문제 해결을 승인합니다.이전 리뷰 코멘트에서 지적된 N+1 쿼리 문제를 배치 조회로 올바르게 해결했습니다.
- 빈 예약 리스트에 대한 early return으로 불필요한 쿼리 방지
distinct()를 사용하여 중복 회원 ID 제거- 한 번의 배치 쿼리로 모든 회원 정보 조회
- Map을 사용한 효율적인 조회
성능 개선이 잘 반영되었습니다.
54-62: 예약 확인 로직을 승인합니다.단일 예약 확인이므로 개별 회원 조회가 적절하며,
MemberDomainService.getById는 회원이 없을 경우 예외를 발생시키므로 null 안전성이 보장됩니다.이전 리뷰 코멘트에서 언급된 이벤트의 민감 정보 노출 우려에 대해: 회원 이름만 이벤트에 포함되므로 알림 목적으로는 적절해 보입니다.
| return reservations.stream() | ||
| .map(reservation -> { | ||
| Member member = membersById.get(reservation.getMemberId()); | ||
| return new ReservationResponse( | ||
| reservation, | ||
| member.getName(), | ||
| member.getPhone(), | ||
| member.getEmail(), | ||
| member.isVip() | ||
| ); | ||
| }) | ||
| .toList(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
회원 정보가 없는 경우에 대한 null 처리가 필요합니다.
Line 42에서 membersById.get(reservation.getMemberId())는 해당 memberId를 가진 회원이 데이터베이스에 없을 경우 null을 반환할 수 있습니다. 이후 Lines 45-48에서 null인 member에 대해 member.getName() 등을 호출하면 NullPointerException이 발생합니다.
데이터 정합성이 보장된다면 발생하지 않을 수 있지만, 방어적 프로그래밍을 위해 null 체크를 추가하는 것을 권장합니다.
다음과 같이 개선할 수 있습니다:
return reservations.stream()
.map(reservation -> {
Member member = membersById.get(reservation.getMemberId());
+ if (member == null) {
+ throw new IllegalStateException("Member not found for reservation: " + reservation.getId());
+ }
return new ReservationResponse(
reservation,
member.getName(),
member.getPhone(),
member.getEmail(),
member.isVip()
);
})
.toList();또는 null인 경우를 필터링하여 제외:
return reservations.stream()
+ .map(reservation -> {
+ Member member = membersById.get(reservation.getMemberId());
+ if (member == null) {
+ return null;
+ }
+ return new ReservationResponse(
+ reservation,
+ member.getName(),
+ member.getPhone(),
+ member.getEmail(),
+ member.isVip()
+ );
+ })
+ .filter(Objects::nonNull)
- .map(reservation -> {
- Member member = membersById.get(reservation.getMemberId());
- return new ReservationResponse(
- reservation,
- member.getName(),
- member.getPhone(),
- member.getEmail(),
- member.isVip()
- );
- })
.toList();📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| return reservations.stream() | |
| .map(reservation -> { | |
| Member member = membersById.get(reservation.getMemberId()); | |
| return new ReservationResponse( | |
| reservation, | |
| member.getName(), | |
| member.getPhone(), | |
| member.getEmail(), | |
| member.isVip() | |
| ); | |
| }) | |
| .toList(); | |
| return reservations.stream() | |
| .map(reservation -> { | |
| Member member = membersById.get(reservation.getMemberId()); | |
| if (member == null) { | |
| throw new IllegalStateException("Member not found for reservation: " + reservation.getId()); | |
| } | |
| return new ReservationResponse( | |
| reservation, | |
| member.getName(), | |
| member.getPhone(), | |
| member.getEmail(), | |
| member.isVip() | |
| ); | |
| }) | |
| .toList(); |
🤖 Prompt for AI Agents
In api-owner/src/main/java/com/wellmeet/reservation/ReservationService.java
around lines 40 to 51, the call membersById.get(reservation.getMemberId()) can
return null and the subsequent member.getX() calls will throw
NullPointerException; add a null check for the retrieved member and handle it
either by supplying safe defaults when constructing ReservationResponse (e.g.,
empty strings and false for isVip) or by filtering out reservations with missing
members before mapping; implement one of these approaches consistently and
ensure no member.get... is called on a null reference.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
api-user/src/test/java/com/wellmeet/favorite/FavoriteServiceTest.java (2)
87-105: member.getId()가 null이어서 테스트가 실제 동작을 검증하지 못함영속되지 않은 Member의 id는 null입니다. 명시적인 memberId 문자열을 사용하여 실제 시나리오를 테스트하도록 수정하세요.
@Test void 즐겨찾기_식당을_추가한다() { - Member member = createMember(); + String memberId = "member-1"; Restaurant restaurant = createRestaurant("restaurant-1", "맛집"); when(restaurantDomainService.getById(restaurant.getId())).thenReturn(restaurant); when(reviewDomainService.getAverageRating(restaurant.getId())).thenReturn(4.2); FavoriteRestaurantResponse result = favoriteService.addFavoriteRestaurant( - member.getId(), + memberId, restaurant.getId() );
112-125: member.getId()가 null이어서 테스트가 실제 동작을 검증하지 못함영속되지 않은 Member의 id는 null입니다. 명시적인 memberId 문자열을 사용하여 실제 시나리오를 테스트하도록 수정하세요.
@Test void 즐겨찾기_식당을_삭제한다() { - Member member = createMember(); + String memberId = "member-1"; Restaurant restaurant = createRestaurant("restaurant-1", "식당"); - FavoriteRestaurant favoriteRestaurant = new FavoriteRestaurant(member.getId(), restaurant.getId()); + FavoriteRestaurant favoriteRestaurant = new FavoriteRestaurant(memberId, restaurant.getId()); when(favoriteRestaurantDomainService.getByMemberIdAndRestaurantId( - member.getId(), + memberId, restaurant.getId() )).thenReturn(favoriteRestaurant); - favoriteService.removeFavoriteRestaurant(member.getId(), restaurant.getId()); + favoriteService.removeFavoriteRestaurant(memberId, restaurant.getId());
♻️ Duplicate comments (1)
api-user/src/test/java/com/wellmeet/favorite/FavoriteServiceTest.java (1)
44-68: member.getId()가 null이어서 테스트가 허위 성공할 수 있음영속되지 않은 Member의 id는 null이므로, 현재 스텁과 검증이 모두 null을 사용하여 테스트가 의미를 잃습니다. 70-80번 라인의 두 번째 테스트처럼 명시적인 memberId 문자열을 사용하도록 수정하세요.
- Member member = createMember(); + String memberId = "member-1"; Restaurant restaurant1 = createRestaurant("restaurant-1", "식당1"); Restaurant restaurant2 = createRestaurant("restaurant-2", "식당2"); - FavoriteRestaurant favorite1 = new FavoriteRestaurant(member.getId(), restaurant1.getId()); - FavoriteRestaurant favorite2 = new FavoriteRestaurant(member.getId(), restaurant2.getId()); + FavoriteRestaurant favorite1 = new FavoriteRestaurant(memberId, restaurant1.getId()); + FavoriteRestaurant favorite2 = new FavoriteRestaurant(memberId, restaurant2.getId()); List<FavoriteRestaurant> favorites = List.of(favorite1, favorite2); - when(favoriteRestaurantDomainService.findAllByMemberId(member.getId())) + when(favoriteRestaurantDomainService.findAllByMemberId(memberId)) .thenReturn(favorites); when(reviewDomainService.getAverageRating("restaurant-1")).thenReturn(4.5); when(reviewDomainService.getAverageRating("restaurant-2")).thenReturn(3.8); when(restaurantDomainService.findAllByIds(List.of(restaurant1.getId(), restaurant2.getId()))) .thenReturn(List.of(restaurant1, restaurant2)); - List<FavoriteRestaurantResponse> result = favoriteService.getFavoriteRestaurants(member.getId()); + List<FavoriteRestaurantResponse> result = favoriteService.getFavoriteRestaurants(memberId);
🧹 Nitpick comments (2)
api-owner/src/test/java/com/wellmeet/reservation/ReservationServiceTest.java (2)
78-80: 이벤트 페이로드까지 검증하세요 (memberName/예약 식별자 등)확인 단계에서 이벤트 타입만 검증하고 있습니다. ArgumentCaptor로 이벤트 내용을 함께 단언하면 리팩터링 목적(memberName 전달) 검증이 명확해집니다.
@@ - reservationService.confirmReservation(reservation.getId()); - - verify(eventPublishService).publishReservationConfirmedEvent( - any(ReservationConfirmedEvent.class) - ); + reservationService.confirmReservation(reservation.getId()); + + org.mockito.ArgumentCaptor<ReservationConfirmedEvent> captor = + org.mockito.ArgumentCaptor.forClass(ReservationConfirmedEvent.class); + verify(eventPublishService).publishReservationConfirmedEvent(captor.capture()); + ReservationConfirmedEvent evt = captor.getValue(); + assertThat(evt.getReservation().getId()).isEqualTo(reservation.getId()); + // 이벤트 필드명에 맞게 조정 (예: getMemberName) + assertThat(evt.getMemberName()).isEqualTo(member.getName());필요 시 도메인 서비스 상호작용(상태 변경/저장 호출)도 함께 verify해 주세요.
42-44: 서비스 테스트 가이드 준수 — runAtSameTime 동시성 및 DTO 매핑 단언 보강 제안
- 동시성: runAtSameTime 유틸을 사용해 예약 확정(또는 좌석 차감 등)의 경쟁 상황을 검증해 주세요(예: 같은 예약 ID에 대해 동시에 confirm 호출 시 단 한 번만 이벤트 발행).
- DTO: getReservations는 size 외에 주요 필드 매핑(memberId, memberName, partySize, status, 시간 등)까지 단언해 주세요.
원하시면 보일러플레이트 테스트 코드 뼈대 제공 가능합니다.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
api-owner/src/test/java/com/wellmeet/reservation/ReservationServiceTest.java(5 hunks)api-user/src/test/java/com/wellmeet/favorite/FavoriteServiceTest.java(2 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
api-{user,owner}/src/test/java/com/wellmeet/**
📄 CodeRabbit inference engine (CLAUDE.md)
api-{user,owner}/src/test/java/com/wellmeet/**: Controller tests must extend BaseControllerTest, use REST Assured, verify HTTP status and response body, include success/failure and auth checks; no mocks, no @DisplayName or given/when/then comments
Service layer tests may be unit tests with mocks (no base class) or integration tests extending BaseServiceTest; must group with @nested, verify DTO mapping and concurrency using runAtSameTime
Files:
api-user/src/test/java/com/wellmeet/favorite/FavoriteServiceTest.javaapi-owner/src/test/java/com/wellmeet/reservation/ReservationServiceTest.java
**/src/test/java/**/*.java
📄 CodeRabbit inference engine (CLAUDE.md)
**/src/test/java/**/*.java: Use AssertJ for assertions across all tests; avoid JUnit assertion styles
Write test method names in Korean with underscores and structure tests using @nested
Do not use @DisplayName in tests
Do not use given/when/then comments; express behavior directly in code
Files:
api-user/src/test/java/com/wellmeet/favorite/FavoriteServiceTest.javaapi-owner/src/test/java/com/wellmeet/reservation/ReservationServiceTest.java
⏰ 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: build-and-push
🔇 Additional comments (1)
api-owner/src/test/java/com/wellmeet/reservation/ReservationServiceTest.java (1)
8-8: MemberDomainService 주입/모킹 적절모듈 분리 방향과 일치합니다. 서비스 로직의 배치 조회 흐름을 테스트로 잘 반영했습니다.
Also applies to: 33-35
| when(memberDomainService.findAllByIds(List.of(member1.getId(), member2.getId()))) | ||
| .thenReturn(List.of(member1, member2)); | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
null memberId로 인한 의미 없는 검증 가능성 — ID를 명시적으로 세팅하세요
현재 Member를 new로 생성해 getId()가 null일 가능성이 높습니다. 그 결과:
- findAllByIds가 [null, null]로 스터빙되어 실제 시나리오와 괴리,
- Reservation에도 null memberId가 저장되어 매핑 검증이 약해집니다.
테스트 신뢰도를 위해 멤버 ID를 고정 값으로 주입하고, 스터빙은 순서에 비의존하게 처리하세요.
아래와 같이 수정 제안합니다.
@@
-import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.argThat;
@@
import java.time.LocalDateTime;
import java.util.List;
+import org.springframework.test.util.ReflectionTestUtils;
@@
- when(memberDomainService.findAllByIds(List.of(member1.getId(), member2.getId())))
- .thenReturn(List.of(member1, member2));
+ when(memberDomainService.findAllByIds(
+ argThat(ids -> ids != null
+ && ids.containsAll(List.of(member1.getId(), member2.getId()))
+ && ids.size() == 2)))
+ .thenReturn(List.of(member1, member2));
@@
- private Member createMember(String name) {
- return new Member(name, "nickname", "email@email.com", "phone");
- }
+ private Member createMember(String name) {
+ Member member = new Member(name, "nickname", "email@email.com", "phone");
+ // 테스트용 결정적 ID 주입
+ ReflectionTestUtils.setField(member, "id", name + "-id");
+ return member;
+ }추가로, getReservations 검증을 단순 size 비교에서 DTO 필드(예: memberId, memberName 등)까지 포함하도록 보강해 주세요.
다음 스크립트로 Member 엔티티의 id 필드 존재/타입을 확인해 주세요. ReflectionTestUtils 사용 가능 여부도 함께 탐색합니다.
Also applies to: 103-103
🏁 Script executed:
#!/bin/bash
# Member 엔티티 id 필드 확인
fd -a 'Member.java' | xargs -I {} rg -nP 'class\s+Member\b|^\s*private\s+(String|Long|UUID)\s+id\s*;' -n --context 2 {}
# ReflectionTestUtils 사용 여부(프로젝트 내 선행 사용 사례 탐색)
rg -n 'ReflectionTestUtils' -g 'src/**/test/**'Length of output: 382
🏁 Script executed:
#!/bin/bash
# ReservationServiceTest.java 전체 확인
fd -a 'ReservationServiceTest.java' -type f
# 찾은 파일의 내용 확인
fd -a 'ReservationServiceTest.java' -type f -exec cat {} \;Length of output: 341
🏁 Script executed:
#!/bin/bash
# Reservation 클래스 구조 확인
fd -a 'Reservation.java' -type f | grep -v Test | xargs -I {} cat {}Length of output: 170
🏁 Script executed:
#!/bin/bash
# BaseEntity 확인
fd -a 'BaseEntity.java' -type f | xargs -I {} cat {}Length of output: 170
🏁 Script executed:
#!/bin/bash
# ReservationServiceTest.java 찾기 및 출력
fd 'ReservationServiceTest\.java$' -exec cat {} \;Length of output: 187
🏁 Script executed:
#!/bin/bash
# Reservation.java 찾기 및 출력 (Test 제외)
fd 'Reservation\.java$' -not -path '*/test/*' -exec cat {} \;Length of output: 230
🏁 Script executed:
#!/bin/bash
# BaseEntity.java 찾기 및 출력
fd 'BaseEntity\.java$' -exec cat {} \;Length of output: 187
🚩 Issue
closed #47
🗣️ 리뷰 요구사항 (선택)
Summary by CodeRabbit