-
Notifications
You must be signed in to change notification settings - Fork 2
[FEAT] api-owner BFF 전환 #72
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. Walkthroughapi-owner가 내부 도메인 모듈 의존성을 제거하고 Spring Cloud OpenFeign/Eureka 기반 원격 호출로 전환합니다. Feign 클라이언트와 DTO들이 추가되고 서비스·이벤트·설정·테스트들이 DTO/클라이언트 중심으로 리팩토링되었습니다. Changes
Sequence Diagram(s)sequenceDiagram
autonumber
participant Client as API Client
participant Controller as ReservationController
participant Service as ReservationService
participant ResClient as ReservationClient
participant MemClient as MemberClient
participant RestClient as RestaurantClient
participant EventPub as EventPublishService
Client->>Controller: GET /owner/reservations?restaurantId=R
Controller->>Service: getReservations(R)
Service->>ResClient: getReservationsByRestaurant(R)
ResClient-->>Service: List<ReservationDTO>
Service->>MemClient: getMembersByIds(ids)
MemClient-->>Service: List<MemberDTO>
Service->>RestClient: getAvailableDate(restaurantId, dateId)
RestClient-->>Service: AvailableDateDTO
Service-->>Controller: List<ReservationResponse>
Controller-->>Client: 200 OK
sequenceDiagram
autonumber
participant Client as API Client
participant Controller as ReservationController
participant Service as ReservationService
participant ResClient as ReservationClient
participant MemClient as MemberClient
participant RestClient as RestaurantClient
participant EventPub as EventPublishService
participant Kafka as KafkaProducer
Client->>Controller: PUT /owner/reservations/{id}/confirm
Controller->>Service: confirmReservation(id)
Service->>ResClient: confirmReservation(id)
Service->>ResClient: getReservation(id)
ResClient-->>Service: ReservationDTO
Service->>MemClient: getMember(memberId)
MemClient-->>Service: MemberDTO
Service->>RestClient: getRestaurant(restaurantId)
RestClient-->>Service: RestaurantDTO
Service->>RestClient: getAvailableDate(restaurantId, dateId)
RestClient-->>Service: AvailableDateDTO
Service->>EventPub: publishEvent(ReservationConfirmedEvent)
EventPub->>Kafka: sendMessage(...)
Service-->>Controller: ReservationResponse
Controller-->>Client: 200 OK
Estimated code review effort🎯 4 (Complex) | ⏱️ ~50 분
Possibly related PRs
Suggested reviewers
Poem
Pre-merge checks and finishing touches❌ Failed checks (3 inconclusive)
✅ Passed checks (1 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
📜 Recent review detailsConfiguration used: CodeRabbit UI Review profile: CHILL Plan: Pro 📒 Files selected for processing (2)
⏰ 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)
🔇 Additional comments (4)
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! 이 PR은 api-owner 모듈을 BFF(Backend-for-Frontend) 아키텍처로 전환하는 중요한 단계입니다. 기존에 api-owner가 각 도메인 서비스에 직접 의존하던 방식에서 벗어나, Feign 클라이언트를 통해 독립적인 마이크로서비스와 통신하도록 변경되었습니다. 이로써 각 서비스의 독립성이 강화되고, 향후 시스템 확장 및 유지보수가 용이해질 것으로 기대됩니다. 또한, 이러한 아키텍처 변화에 맞춰 테스트 전략도 Mock 기반으로 전환되어, 더욱 견고하고 빠른 테스트 환경을 구축했습니다. 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
api-owner 서비스를 BFF 아키텍처로 성공적으로 리팩토링한 점이 인상적입니다. Feign 클라이언트를 도입하여 도메인 모듈에 대한 직접 의존성을 제거하고, Eureka를 통한 서비스 디스커버리를 적용한 것은 마이크로서비스 전환의 좋은 예시입니다. 특히 테스트 전략을 DB 통합 테스트에서 Mock 기반의 단위/통합 테스트로 전환하여 테스트 속도와 안정성을 높인 점이 돋보입니다. 리뷰에서는 Feign 오류 처리 개선, 서비스 간 통신 효율성 증대, 테스트 코드의 유지보수성 향상에 대한 몇 가지 제안을 포함했습니다.
| return switch (response.status()) { | ||
| case 400 -> new IllegalArgumentException("잘못된 요청입니다: " + response.reason()); | ||
| case 404 -> new IllegalArgumentException("요청한 리소스를 찾을 수 없습니다: " + response.reason()); | ||
| case 500 -> new RuntimeException("서버 내부 오류가 발생했습니다: " + response.reason()); | ||
| case 503 -> new RuntimeException("서비스를 사용할 수 없습니다: " + response.reason()); | ||
| default -> defaultDecoder.decode(methodKey, response); | ||
| }; |
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.
404 Not Found 에러에 대해 IllegalArgumentException을 던지는 것은 의미상 맞지 않습니다. IllegalArgumentException은 잘못된 인수를 의미하는 반면, 404는 리소스를 찾을 수 없음을 나타냅니다. 이로 인해 글로벌 예외 처리기에서 오류를 잘못 해석할 수 있습니다. ResourceNotFoundException과 같은 사용자 정의 예외를 생성하여 사용하거나, 상황에 더 적합한 기존 예외를 사용하는 것이 좋습니다. 이렇게 하면 오류 상태를 더 명확하게 나타내고 더 나은 오류 처리가 가능해집니다.
| reservationClient.confirmReservation(reservationId); | ||
|
|
||
| Member member = memberDomainService.getById(reservation.getMemberId()); | ||
| var restaurant = restaurantDomainService.getById(reservation.getRestaurantId()); | ||
| var availableDate = restaurantDomainService.getAvailableDate( | ||
| reservation.getAvailableDateId(), reservation.getRestaurantId()); | ||
| ReservationDTO reservation = reservationClient.getReservation(reservationId); |
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.
confirmReservation 메소드에서 예약을 확정한 후(confirmReservation) 다시 예약 정보를 조회(getReservation)하기 위해 두 번의 네트워크 호출이 발생하고 있습니다. ReservationClient의 confirmReservation 메소드가 void 대신 업데이트된 ReservationDTO를 반환하도록 수정하면 네트워크 호출을 한 번으로 줄여 성능을 향상시킬 수 있습니다.
이를 위해 ReservationClient 인터페이스의 confirmReservation 메소드 시그니처도 ReservationDTO confirmReservation(@PathVariable("id") Long id); 와 같이 변경해야 합니다.
| reservationClient.confirmReservation(reservationId); | |
| Member member = memberDomainService.getById(reservation.getMemberId()); | |
| var restaurant = restaurantDomainService.getById(reservation.getRestaurantId()); | |
| var availableDate = restaurantDomainService.getAvailableDate( | |
| reservation.getAvailableDateId(), reservation.getRestaurantId()); | |
| ReservationDTO reservation = reservationClient.getReservation(reservationId); | |
| ReservationDTO reservation = reservationClient.confirmReservation(reservationId); |
| package com.wellmeet.client.dto.request; | ||
|
|
||
| import lombok.AllArgsConstructor; | ||
| import lombok.Builder; | ||
| import lombok.Getter; | ||
| import lombok.NoArgsConstructor; | ||
|
|
||
| @Getter | ||
| @Builder | ||
| @NoArgsConstructor | ||
| @AllArgsConstructor | ||
| public class CreateReservationDTO { | ||
|
|
||
| private String restaurantId; | ||
| private Long availableDateId; | ||
| private String memberId; | ||
| private int partySize; | ||
| private 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.
| private OperatingHoursResponse createOperatingHoursResponse() { | ||
| OperatingHoursResponse response = new OperatingHoursResponse(); | ||
| List<OperatingHoursResponse.DayHours> dayHoursList = List.of( | ||
| createDayHours(DayOfWeek.MONDAY, true, "09:00", "22:00", "15:00", "17:00"), | ||
| createDayHours(DayOfWeek.TUESDAY, true, "09:00", "22:00", "15:00", "17:00"), | ||
| createDayHours(DayOfWeek.WEDNESDAY, true, "09:00", "22:00", "15:00", "17:00") | ||
| ); | ||
|
|
||
| try { | ||
| java.lang.reflect.Field field = OperatingHoursResponse.class.getDeclaredField("operatingHours"); | ||
| field.setAccessible(true); | ||
| field.set(response, dayHoursList); | ||
| } catch (Exception e) { | ||
| throw new RuntimeException(e); | ||
| } | ||
|
|
||
| return response; | ||
| } | ||
|
|
||
| private OperatingHoursResponse createUpdatedOperatingHoursResponse() { | ||
| OperatingHoursResponse response = new OperatingHoursResponse(); | ||
| List<OperatingHoursResponse.DayHours> dayHoursList = List.of( | ||
| createDayHours(DayOfWeek.MONDAY, true, "09:00", "21:00", "13:00", "14:00"), | ||
| createDayHours(DayOfWeek.TUESDAY, false, null, null, null, null), | ||
| createDayHours(DayOfWeek.WEDNESDAY, true, "10:00", "20:00", "13:00", "14:00") | ||
| ); | ||
|
|
||
| try { | ||
| java.lang.reflect.Field field = OperatingHoursResponse.class.getDeclaredField("operatingHours"); | ||
| field.setAccessible(true); | ||
| field.set(response, dayHoursList); | ||
| } catch (Exception e) { | ||
| throw new RuntimeException(e); | ||
| } | ||
|
|
||
| return response; | ||
| } | ||
|
|
||
| private OperatingHoursResponse.DayHours createDayHours(DayOfWeek dayOfWeek, boolean operating, | ||
| String open, String close, | ||
| String breakStart, String breakEnd) { | ||
| OperatingHoursResponse.DayHours dayHours = new OperatingHoursResponse.DayHours(); | ||
|
|
||
| try { | ||
| java.lang.reflect.Field dayOfWeekField = OperatingHoursResponse.DayHours.class.getDeclaredField( | ||
| "dayOfWeek"); | ||
| dayOfWeekField.setAccessible(true); | ||
| dayOfWeekField.set(dayHours, dayOfWeek); | ||
|
|
||
| java.lang.reflect.Field operatingField = OperatingHoursResponse.DayHours.class.getDeclaredField( | ||
| "operating"); | ||
| operatingField.setAccessible(true); | ||
| operatingField.set(dayHours, operating); | ||
|
|
||
| if (operating) { | ||
| java.lang.reflect.Field openField = OperatingHoursResponse.DayHours.class.getDeclaredField("open"); | ||
| openField.setAccessible(true); | ||
| openField.set(dayHours, LocalTime.parse(open)); | ||
|
|
||
| java.lang.reflect.Field closeField = OperatingHoursResponse.DayHours.class.getDeclaredField("close"); | ||
| closeField.setAccessible(true); | ||
| closeField.set(dayHours, LocalTime.parse(close)); | ||
|
|
||
| OperatingHoursResponse.BreakTime breakTime = new OperatingHoursResponse.BreakTime(); | ||
| java.lang.reflect.Field startField = OperatingHoursResponse.BreakTime.class.getDeclaredField("start"); | ||
| startField.setAccessible(true); | ||
| startField.set(breakTime, LocalTime.parse(breakStart)); | ||
|
|
||
| java.lang.reflect.Field endField = OperatingHoursResponse.BreakTime.class.getDeclaredField("end"); | ||
| endField.setAccessible(true); | ||
| endField.set(breakTime, LocalTime.parse(breakEnd)); | ||
|
|
||
| java.lang.reflect.Field breakTimeField = OperatingHoursResponse.DayHours.class.getDeclaredField( | ||
| "breakTime"); | ||
| breakTimeField.setAccessible(true); | ||
| breakTimeField.set(dayHours, breakTime); | ||
| } | ||
| } catch (Exception e) { | ||
| throw new RuntimeException(e); | ||
| } | ||
|
|
||
| return dayHours; | ||
| } |
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.
테스트 데이터 생성을 위해 createOperatingHoursResponse, createDayHours 등의 헬퍼 메소드에서 리플렉션을 사용하는 것은 테스트 코드를 이해하기 어렵게 만들고 깨지기 쉽게 만듭니다. OperatingHoursResponse와 DayHours 클래스에는 BusinessHourDTO를 사용하는 public 생성자가 있으므로, 테스트에서 BusinessHourDTO 객체를 생성하여 이 생성자들을 활용하는 것이 좋습니다. 이렇게 하면 리플렉션 없이 더 깔끔하고 유지보수하기 쉬운 테스트 코드를 작성할 수 있습니다.
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: 7
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
api-owner/src/main/java/com/wellmeet/reservation/ReservationService.java (1)
48-60: 회원 정보 맵에서 null 반환 시 NPE 위험
memberClient.getMembersByIds(...)가 모든 회원을 반환하지 못하면membersById.get(...)가null을 돌려주고 즉시member.getName()호출에서 NPE가 발생합니다. 외부 서비스 통신에서는 부분 응답이 충분히 가능하므로 방어 코드가 필요합니다.-import java.util.Map; -import java.util.function.Function; -import java.util.stream.Collectors; +import java.util.Map; +import java.util.Optional; +import java.util.function.Function; +import java.util.stream.Collectors; @@ - MemberDTO member = membersById.get(reservation.getMemberId()); + MemberDTO member = Optional.ofNullable(membersById.get(reservation.getMemberId())) + .orElseThrow(() -> new IllegalStateException( + "Member not found for id: " + reservation.getMemberId()));이렇게 하면 누락 상황을 명확한 예외로 처리하거나, 필요 시 대체 흐름을 추가할 수 있습니다.
🧹 Nitpick comments (16)
claudedocs/phase5-bff-migration-plan.md (7)
119-124: Feign 설정에 오류 처리 및 복원력 패턴 추가 고려현재 FeignErrorDecoder만 언급되고 구체적인 오류 처리 전략과 복원력 패턴(Circuit Breaker, Retry 정책)이 문서화되지 않았습니다. 다운스트림 서비스 장애 시 전체 요청이 실패하거나 무한 대기할 수 있는 상황을 고려하세요.
다음을 추가로 검토하시기 바랍니다:
- Resilience4j Circuit Breaker: api-owner → api-user 연쇄 장애 방지
- Retry 정책: 일시적 네트워크 오류에 대한 자동 재시도 (지수 백오프)
- 타임아웃: 현재 전역 설정(5000ms)만 있으며, 메서드별 커스터마이징 필요 여부 검토
- Fallback 전략: 특정 서비스 장애 시 graceful degradation 방법
예시:
@FeignClient(name = "reservation-service", configuration = FeignConfig.class, fallback = ReservationClientFallback.class) public interface ReservationClient { ... } @Component class ReservationClientFallback implements ReservationClient { @Override public List<ReservationDTO> getReservationsByRestaurant(String id) { return Collections.emptyList(); // 또는 cached data } }
146-295: 테스트 마이그레이션: 통합 테스트 및 Feign 클라이언트 검증 전략 부재Service와 Controller 테스트는 완전히 Mock 기반으로 전환하도록 계획되어 있습니다. 다만 실제 Feign 클라이언트 호출을 검증하는 통합 테스트 전략이 누락되어 있어 마이크로서비스 간 통신 오류를 프로덕션 배포 후에야 발견할 수 있는 위험이 있습니다.
다음을 고려하세요:
1. Feign Client 통합 테스트 (WireMock 또는 Testcontainers 활용):
@SpringBootTest @ActiveProfiles("integration") class ReservationClientIntegrationTest { @RegisterExtension static WireMockExtension wireMock = WireMockExtension.newInstance() .options(wireMockConfig().port(8089)) .build(); @Test void feign클라이언트가_실제_API를_호출할수있다() { // WireMock stub 설정 wireMock.stubFor(get(urlPathEqualTo("/reservation/restaurant-1")) .willReturn(okJson("..."))); List<ReservationDTO> result = reservationClient.getReservationsByRestaurant("restaurant-1"); assertThat(result).isNotEmpty(); } }2. Contract Testing 도입 시기 명확화:
- 현재: "Phase 6 이후 도입" (라인 8)
- 권장: Phase 5-2 완료 후 즉시 Contract Test 추가 (CDC - Consumer Driven Contracts)
3. 테스트 유형별 분류:
- Unit: Service Mock 테스트 (현재 계획)
- Integration: Feign Client 호출 테스트 (누락)
- E2E: 전체 마이크로서비스 스택 검증 (라인 777-782 수동 테스트만 있음)
1-20: 분산 환경에서의 모니터링 및 추적(Observability) 전략 문서화 필요마이크로서비스 마이그레이션 후 Feign 호출 실패, 느린 응답, 또는 부분 장애를 신속하게 진단하기 위한 모니터링 전략이 문서화되지 않았습니다.
추가 계획 섹션을 권장합니다:
분산 추적 (Distributed Tracing):
- OpenTelemetry 또는 Spring Cloud Sleuth + Zipkin 도입
- Feign 요청에 X-Trace-Id 전파
- 멀티 서비스 요청 흐름 시각화
로깅 전략:
@Aspect @Component public class FeignLoggingAspect { @Around("@annotation(com.wellmeet.annotation.FeignCall)") public Object logFeignCall(ProceedingJoinPoint pjp) { long start = System.currentTimeMillis(); try { return pjp.proceed(); } finally { long duration = System.currentTimeMillis() - start; // 메트릭 기록: 호출 시간, 실패율, 타임아웃 횟수 } } }모니터링 지표:
- Feign 호출 응답 시간 분포
- 서비스별 오류율 및 타임아웃 발생률
- Circuit Breaker 상태 변화
456-486: 마이그레이션 실패 또는 롤백 전략 부재Phase 5-1 완료 후 프로덕션 배포 시 문제가 발생할 경우의 롤백 계획이 없습니다. 예를 들어:
- Feign 클라이언트 장애로 모든 예약 API가 실패하는 경우
- 다운스트림 서비스의 API 변경으로 호환성 깨짐
- Eureka 서비스 발견 실패
다음 항목을 마이그레이션 계획에 추가하세요:
1. 롤백 계획:
# application.yml에 Feature Flag 추가 feign: fallback-to-domain-service: false # true로 설정 시 Feign 대신 직접 호출2. 점진적 마이그레이션 (Canary Deployment):
- 처음엔 10% 트래픽만 Feign으로 라우팅
- 모니터링 후 100%로 확대
3. Blue-Green Deployment:
- Blue (기존): domain-* 의존성 유지
- Green (신규): Feign 클라이언트 사용
- 이상 시 즉시 Blue로 회귀
609-647: Phase 5-2 보상 트랜잭션 로직: 예외 처리 상세화 권장ReservationService의 보상 트랜잭션 예제(라인 640-644)에서
catch (Exception e)블록이 너무 광범위하며, 특정 오류 시나리오에 대한 처리가 불명확합니다.다음과 같이 상세화할 것을 권장합니다:
try { // 2. Feign 호출 MemberDTO member = memberClient.getMember(memberId); availableDateClient.decreaseCapacity(...); ReservationDTO reservation = reservationClient.create(...); eventPublishService.publishReservationCreated(reservation); return buildResponse(reservation, member); } catch (FeignException.ServiceUnavailable | FeignException.GatewayTimeout e) { // 다운스트림 서비스 일시 장애 - 보상 + 재시도 대기 availableDateClient.increaseCapacity(...); throw new ServiceTemporarilyUnavailableException("Retry after delay", e); } catch (FeignException.BadRequest e) { // 클라이언트 오류 - 보상 후 거부 availableDateClient.increaseCapacity(...); throw new InvalidReservationRequestException(e.getMessage(), e); } catch (Exception e) { // 예상 외 오류 - 보상 + 알림 logger.error("Unexpected error during reservation", e); availableDateClient.increaseCapacity(...); alertService.notifyAdmins("Compensation failed", e); throw e; }또한 보상 트랜잭션 자체 실패 시나리오도 고려하세요 (예: increaseCapacity 호출 실패 시 데이터 불일치 위험).
510-551: Phase 5-1 체크리스트: 테스트 검증 항목 구체화 권장"모든 테스트 통과 (Mock 기반)"(라인 548) 항목이 너무 광범위합니다. Phase 5-2의 검증 항목(라인 819-825)은 더 구체적입니다.
Phase 5-1 체크리스트 개선 제안:
### 검증 - [ ] 빌드 성공 (domain-* 의존성 없이) - [ ] 모든 테스트 통과: - [ ] Service Mock 테스트 (ReservationServiceTest: 2개, RestaurantServiceTest: 1개) - [ ] Controller MockBean 테스트 (ReservationControllerTest: 3개, RestaurantControllerTest: 2개) - [ ] 총 테스트 실행 시간 < 10초 (DB 접근 없음) - [ ] Docker Compose 전체 서비스 작동 (eureka + api-owner + domain-reservation + ...) - [ ] Eureka 대시보드에서 api-owner 등록 확인 - [ ] Feign 호출 성공: - curl -X GET http://localhost:8087/owner/reservation/restaurant-1 - 응답 상태 200, 응답 시간 < 1000ms이렇게 하면 체크리스트 항목의 명확성이 높아지고 팀원들이 검증 기준을 정확히 이해할 수 있습니다.
65-86: application.yml: 환경별 Feign 설정 분리 고려현재 전역 Feign 설정(connectTimeout, readTimeout 모두 5000ms)만 제시되어 있습니다. 개발/테스트/프로덕션 환경에서 다른 타임아웃이 필요할 수 있습니다.
다음과 같이 환경별 프로파일 제공을 권장합니다:
# application-dev.yml feign: client: config: default: connectTimeout: 2000 readTimeout: 3000 loggerLevel: FULL # application-prod.yml feign: client: config: default: connectTimeout: 3000 readTimeout: 5000 loggerLevel: BASIC reservation-service: # 메서드별 커스터마이징 readTimeout: 10000 # 예약 조회는 더 오래 기다림api-owner/src/main/java/com/wellmeet/client/dto/request/CreateReservationDTO.java (1)
17-17: primitive int 타입 사용 고려사항
partySize필드가 primitiveint타입으로 선언되어 있어 null 값을 가질 수 없으며, 값이 설정되지 않으면 기본값 0을 갖게 됩니다. 예약 인원이 0명인 것은 일반적으로 유효하지 않은 값입니다.
Integer로 변경하여 명시적으로 값이 설정되었는지 확인할 수 있도록 하는 것을 고려해 보세요.필요시 다음과 같이 수정할 수 있습니다:
- private int partySize; + private Integer partySize;api-owner/src/main/java/com/wellmeet/ApiOwnerApplication.java (1)
8-8: LGTM! Feign 클라이언트 지원이 활성화되었습니다.
@EnableFeignClients어노테이션이 올바르게 추가되어 마이크로서비스 호출을 위한 Feign 클라이언트를 사용할 수 있습니다.더 명확한 구성을 원한다면
basePackages를 명시할 수 있습니다:-@EnableFeignClients +@EnableFeignClients(basePackages = "com.wellmeet.client")api-owner/src/main/java/com/wellmeet/client/dto/MemberDTO.java (1)
14-22: boolean 필드 명명 규칙의 일관성을 검토하세요.Line 22의
isVip필드는 JavaBeans 명명 규칙과 일관성 문제가 있습니다. 다른 boolean 필드들(reservationEnabled,remindEnabled,reviewEnabled)은is접두사 없이 명명되었지만,isVip만is접두사를 사용합니다.일관성을 위해 다음과 같이 수정하는 것을 권장합니다:
- private boolean isVip; + private boolean vip;참고: Lombok의
@Getter는 boolean 필드에 대해 자동으로isVip()메서드를 생성하므로, 필드명을vip로 변경해도 getter는isVip()으로 생성됩니다.api-owner/src/main/java/com/wellmeet/client/dto/BusinessHourDTO.java (1)
15-21: boolean 필드 명명의 일관성을 검토하세요.Line 17의
isOperating필드명이 관련 DTO들과 일관성이 없습니다:
UpdateOperatingHoursRequest.DayHours는operating사용UpdateOperatingHoursDTO.DayHoursDTO는isOperating사용이로 인해 DTO 변환 시 혼란이 발생할 수 있습니다.
일관성을 위해
operating으로 통일하는 것을 권장합니다:- private boolean isOperating; + private boolean operating;또는 프로젝트 전체에서 boolean 필드 명명 규칙을 정의하고 모든 DTO에 일관되게 적용하세요.
api-owner/src/main/java/com/wellmeet/client/dto/request/UpdateOperatingHoursDTO.java (1)
14-29: UpdateOperatingHoursRequest와 구조가 중복됩니다.
UpdateOperatingHoursDTO와UpdateOperatingHoursRequest.DayHours는 거의 동일한 필드를 가지고 있습니다:
- 둘 다 운영 시간 업데이트를 위한 유사한 구조
- 주요 차이점:
dayOfWeek타입 (String vs Enum),isOperatingvsoperating필드명이는 DTO 간 변환 로직이 필요하며, 향후 유지보수 시 두 클래스를 동시에 수정해야 하는 부담이 있습니다.
다음을 고려하세요:
- 가능하다면 하나의 DTO로 통합
- 또는 명확한 변환 유틸리티 메서드 제공
- 두 DTO의 존재 이유와 역할을 문서화 (요청 DTO vs Feign 클라이언트 DTO)
api-owner/src/main/java/com/wellmeet/config/FeignErrorDecoder.java (1)
17-23: 예외 타입이 HTTP 에러의 의미를 정확히 반영하지 못합니다.현재 구현은 모든 HTTP 에러를
IllegalArgumentException또는 일반RuntimeException으로 매핑하고 있습니다. 이는 다음과 같은 문제가 있습니다:
- 400/404를
IllegalArgumentException으로 매핑하는 것은 이 예외가 주로 메서드 인자 검증 실패를 나타내는데 사용되므로 의미론적으로 맞지 않습니다.- 500/503을 일반
RuntimeException으로 매핑하면 호출자가 에러 유형을 구분하기 어렵습니다.- 원본 응답의 body 정보가 손실됩니다.
커스텀 예외 계층 구조를 도입하는 것을 권장합니다:
+public class FeignClientException extends RuntimeException { + private final int status; + private final String reason; + + public FeignClientException(int status, String reason, String message) { + super(message); + this.status = status; + this.reason = reason; + } +} + +public class FeignBadRequestException extends FeignClientException { } +public class FeignNotFoundException extends FeignClientException { } +public class FeignServerException extends FeignClientException { } public Exception decode(String methodKey, Response response) { log.error("Feign error occurred - method: {}, status: {}, reason: {}", methodKey, response.status(), response.reason()); return switch (response.status()) { - case 400 -> new IllegalArgumentException("잘못된 요청입니다: " + response.reason()); - case 404 -> new IllegalArgumentException("요청한 리소스를 찾을 수 없습니다: " + response.reason()); - case 500 -> new RuntimeException("서버 내부 오류가 발생했습니다: " + response.reason()); - case 503 -> new RuntimeException("서비스를 사용할 수 없습니다: " + response.reason()); + case 400 -> new FeignBadRequestException(response.status(), response.reason(), "잘못된 요청입니다: " + response.reason()); + case 404 -> new FeignNotFoundException(response.status(), response.reason(), "요청한 리소스를 찾을 수 없습니다: " + response.reason()); + case 500, 503 -> new FeignServerException(response.status(), response.reason(), "서버 오류가 발생했습니다: " + response.reason()); default -> defaultDecoder.decode(methodKey, response); }; }api-owner/src/main/java/com/wellmeet/restaurant/RestaurantService.java (1)
37-51: 수동 DTO 매핑을 자동화하여 코드 중복을 줄이세요.현재 요청 DTO를 클라이언트 DTO로 변환하는 과정이 수동으로 이루어지고 있어 장황하고 오류가 발생하기 쉽습니다. MapStruct와 같은 매핑 라이브러리를 사용하거나 DTO 간의 변환 메서드를 도입하는 것을 권장합니다.
예시:
// In UpdateOperatingHoursRequest.DayHours public UpdateOperatingHoursDTO.DayHoursDTO toClientDTO() { return UpdateOperatingHoursDTO.DayHoursDTO.builder() .dayOfWeek(this.dayOfWeek.name()) .isOperating(this.isOperating) .open(this.open) .close(this.close) .breakStart(this.breakStart) .breakEnd(this.breakEnd) .build(); } // In service List<UpdateOperatingHoursDTO.DayHoursDTO> dayHoursList = request.getOperatingHours() .stream() .map(DayHours::toClientDTO) .toList();api-owner/src/test/java/com/wellmeet/restaurant/RestaurantServiceTest.java (1)
170-236: 테스트 데이터 생성 헬퍼 메서드가 중복을 효과적으로 줄입니다.
createBusinessHourDTOList()헬퍼 메서드가 테스트 간 중복을 제거하고 가독성을 향상시킵니다.약간의 개선 사항으로, 테스트 데이터 빌더 패턴을 고려할 수 있습니다:
private BusinessHourDTO.BusinessHourDTOBuilder defaultBusinessHour() { return BusinessHourDTO.builder() .isOperating(true) .open(LocalTime.of(9, 0)) .close(LocalTime.of(22, 0)) .breakStart(LocalTime.of(15, 0)) .breakEnd(LocalTime.of(17, 0)); } private List<BusinessHourDTO> createBusinessHourDTOList() { String[] days = {"MONDAY", "TUESDAY", "WEDNESDAY", "THURSDAY", "FRIDAY", "SATURDAY", "SUNDAY"}; List<BusinessHourDTO> result = new ArrayList<>(); for (int i = 0; i < days.length; i++) { boolean isWeekend = i >= 5; result.add(defaultBusinessHour() .id((long) (i + 1)) .dayOfWeek(days[i]) .isOperating(!isWeekend) .open(isWeekend ? null : LocalTime.of(9, 0)) .close(isWeekend ? null : LocalTime.of(22, 0)) .breakStart(isWeekend ? null : LocalTime.of(15, 0)) .breakEnd(isWeekend ? null : LocalTime.of(17, 0)) .build()); } return result; }하지만 현재 구현도 충분히 명확하고 읽기 쉽습니다.
api-owner/src/test/java/com/wellmeet/reservation/ReservationServiceTest.java (1)
50-71: 테스트 로직이 올바르게 마이그레이션되었습니다.Feign 클라이언트 기반의 테스트로 정확하게 전환되었습니다. 다만 선택적으로 다음 개선을 고려해볼 수 있습니다:
- 현재
assertThat(expectedReservations).hasSize(reservations.size())만 검증하고 있는데, 반환된ReservationResponse의 실제 필드값(예: memberId, restaurantId, partySize 등)도 검증하면 더 견고한 테스트가 될 수 있습니다.예시:
assertThat(expectedReservations).hasSize(reservations.size()); +assertThat(expectedReservations.get(0).getMemberId()).isEqualTo(member1.getId()); +assertThat(expectedReservations.get(0).getPartySize()).isEqualTo(4);
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (46)
api-owner/build.gradle(1 hunks)api-owner/src/main/java/com/wellmeet/ApiOwnerApplication.java(1 hunks)api-owner/src/main/java/com/wellmeet/client/MemberClient.java(1 hunks)api-owner/src/main/java/com/wellmeet/client/OwnerClient.java(1 hunks)api-owner/src/main/java/com/wellmeet/client/ReservationClient.java(1 hunks)api-owner/src/main/java/com/wellmeet/client/RestaurantClient.java(1 hunks)api-owner/src/main/java/com/wellmeet/client/dto/AvailableDateDTO.java(1 hunks)api-owner/src/main/java/com/wellmeet/client/dto/BusinessHourDTO.java(1 hunks)api-owner/src/main/java/com/wellmeet/client/dto/MemberDTO.java(1 hunks)api-owner/src/main/java/com/wellmeet/client/dto/OwnerDTO.java(1 hunks)api-owner/src/main/java/com/wellmeet/client/dto/ReservationDTO.java(1 hunks)api-owner/src/main/java/com/wellmeet/client/dto/RestaurantDTO.java(1 hunks)api-owner/src/main/java/com/wellmeet/client/dto/request/CreateReservationDTO.java(1 hunks)api-owner/src/main/java/com/wellmeet/client/dto/request/MemberIdsRequest.java(1 hunks)api-owner/src/main/java/com/wellmeet/client/dto/request/UpdateOperatingHoursDTO.java(1 hunks)api-owner/src/main/java/com/wellmeet/client/dto/request/UpdateRestaurantDTO.java(1 hunks)api-owner/src/main/java/com/wellmeet/config/FeignConfig.java(1 hunks)api-owner/src/main/java/com/wellmeet/config/FeignErrorDecoder.java(1 hunks)api-owner/src/main/java/com/wellmeet/global/event/event/ReservationConfirmedEvent.java(2 hunks)api-owner/src/main/java/com/wellmeet/reservation/ReservationService.java(3 hunks)api-owner/src/main/java/com/wellmeet/reservation/dto/DayOfWeek.java(1 hunks)api-owner/src/main/java/com/wellmeet/reservation/dto/ReservationResponse.java(3 hunks)api-owner/src/main/java/com/wellmeet/restaurant/RestaurantService.java(2 hunks)api-owner/src/main/java/com/wellmeet/restaurant/dto/OperatingHoursResponse.java(4 hunks)api-owner/src/main/java/com/wellmeet/restaurant/dto/ReservationStatus.java(1 hunks)api-owner/src/main/java/com/wellmeet/restaurant/dto/UpdateOperatingHoursRequest.java(1 hunks)api-owner/src/main/resources/application-dev.yml(1 hunks)api-owner/src/main/resources/application-local.yml(1 hunks)api-owner/src/main/resources/application-test.yml(1 hunks)api-owner/src/test/java/com/wellmeet/BaseControllerTest.java(0 hunks)api-owner/src/test/java/com/wellmeet/global/event/EventPublishServiceTest.java(0 hunks)api-owner/src/test/java/com/wellmeet/global/event/listener/ReservationEventListenerTest.java(0 hunks)api-owner/src/test/java/com/wellmeet/reservation/ReservationControllerTest.java(1 hunks)api-owner/src/test/java/com/wellmeet/reservation/ReservationServiceTest.java(4 hunks)api-owner/src/test/java/com/wellmeet/restaurant/DomainRestaurantControllerTest.java(0 hunks)api-owner/src/test/java/com/wellmeet/restaurant/DomainRestaurantServiceTest.java(0 hunks)api-owner/src/test/java/com/wellmeet/restaurant/RestaurantControllerTest.java(1 hunks)api-owner/src/test/java/com/wellmeet/restaurant/RestaurantServiceTest.java(1 hunks)api-user/src/main/resources/application-dev.yml(1 hunks)api-user/src/main/resources/application-local.yml(1 hunks)api-user/src/main/resources/application-test.yml(1 hunks)batch-reminder/src/main/resources/application-dev.yml(1 hunks)batch-reminder/src/main/resources/application-local.yml(1 hunks)batch-reminder/src/main/resources/application-test.yml(1 hunks)claudedocs/phase4-detailed-plan.md(0 hunks)claudedocs/phase5-bff-migration-plan.md(1 hunks)
💤 Files with no reviewable changes (6)
- api-owner/src/test/java/com/wellmeet/global/event/EventPublishServiceTest.java
- api-owner/src/test/java/com/wellmeet/restaurant/DomainRestaurantServiceTest.java
- api-owner/src/test/java/com/wellmeet/global/event/listener/ReservationEventListenerTest.java
- api-owner/src/test/java/com/wellmeet/restaurant/DomainRestaurantControllerTest.java
- claudedocs/phase4-detailed-plan.md
- api-owner/src/test/java/com/wellmeet/BaseControllerTest.java
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-07-10T02:52:48.337Z
Learnt from: unifolio0
Repo: Team-soft-macaron/WellMeet-Backend PR: 14
File: src/main/java/com/wellmeet/recommend/dto/RecommendRestaurantResponse.java:3-3
Timestamp: 2025-07-10T02:52:48.337Z
Learning: The Restaurant class is located at `com.wellmeet.restaurant.domain.Restaurant` after the package refactoring in the WellMeet-Backend project, not in a nested model package structure.
Applied to files:
api-owner/src/main/java/com/wellmeet/restaurant/dto/ReservationStatus.javaapi-owner/src/test/java/com/wellmeet/restaurant/RestaurantServiceTest.javaapi-owner/src/main/java/com/wellmeet/restaurant/dto/OperatingHoursResponse.javaapi-owner/src/main/java/com/wellmeet/restaurant/RestaurantService.javaapi-owner/src/main/java/com/wellmeet/reservation/ReservationService.javaapi-owner/src/main/java/com/wellmeet/client/dto/request/UpdateRestaurantDTO.javaapi-owner/src/test/java/com/wellmeet/reservation/ReservationServiceTest.javaapi-owner/src/main/java/com/wellmeet/client/RestaurantClient.javaapi-owner/src/main/java/com/wellmeet/client/ReservationClient.javaapi-owner/src/test/java/com/wellmeet/restaurant/RestaurantControllerTest.javaapi-owner/src/main/java/com/wellmeet/client/dto/RestaurantDTO.java
🧬 Code graph analysis (14)
api-owner/src/test/java/com/wellmeet/restaurant/RestaurantServiceTest.java (2)
api-owner/src/test/java/com/wellmeet/restaurant/RestaurantControllerTest.java (3)
Nested(26-50)Nested(52-89)Nested(91-130)api-owner/src/test/java/com/wellmeet/restaurant/DomainRestaurantServiceTest.java (1)
ExtendWith(30-177)
api-owner/src/main/java/com/wellmeet/client/dto/ReservationDTO.java (3)
api-owner/src/main/java/com/wellmeet/client/dto/AvailableDateDTO.java (1)
Getter(10-22)api-owner/src/main/java/com/wellmeet/client/dto/MemberDTO.java (1)
Getter(8-23)api-owner/src/main/java/com/wellmeet/client/dto/request/CreateReservationDTO.java (1)
Getter(8-19)
api-owner/src/test/java/com/wellmeet/reservation/ReservationControllerTest.java (2)
api-owner/src/test/java/com/wellmeet/restaurant/RestaurantControllerTest.java (3)
Nested(26-50)Nested(52-89)Nested(91-130)api-owner/src/test/java/com/wellmeet/reservation/ReservationServiceTest.java (2)
Nested(46-72)Nested(74-107)
api-owner/src/main/java/com/wellmeet/client/dto/AvailableDateDTO.java (2)
api-owner/src/main/java/com/wellmeet/client/dto/ReservationDTO.java (1)
Getter(9-24)api-owner/src/main/java/com/wellmeet/client/dto/request/CreateReservationDTO.java (1)
Getter(8-19)
api-owner/src/main/java/com/wellmeet/client/dto/request/UpdateRestaurantDTO.java (1)
api-owner/src/main/java/com/wellmeet/client/dto/RestaurantDTO.java (1)
Getter(8-21)
api-owner/src/main/java/com/wellmeet/client/dto/request/UpdateOperatingHoursDTO.java (3)
api-owner/src/main/java/com/wellmeet/restaurant/dto/OperatingHoursResponse.java (3)
Getter(10-53)Getter(22-39)Getter(41-52)api-owner/src/main/java/com/wellmeet/restaurant/dto/UpdateOperatingHoursRequest.java (2)
Getter(10-29)Getter(17-28)api-owner/src/main/java/com/wellmeet/client/dto/BusinessHourDTO.java (1)
Getter(9-22)
api-owner/src/main/java/com/wellmeet/client/dto/BusinessHourDTO.java (3)
api-owner/src/main/java/com/wellmeet/restaurant/dto/OperatingHoursResponse.java (3)
Getter(10-53)Getter(22-39)Getter(41-52)api-owner/src/main/java/com/wellmeet/restaurant/dto/UpdateOperatingHoursRequest.java (2)
Getter(10-29)Getter(17-28)api-owner/src/main/java/com/wellmeet/client/dto/request/UpdateOperatingHoursDTO.java (2)
Getter(10-30)Getter(18-29)
api-owner/src/main/java/com/wellmeet/client/dto/OwnerDTO.java (1)
api-owner/src/main/java/com/wellmeet/client/dto/MemberDTO.java (1)
Getter(8-23)
api-owner/src/main/java/com/wellmeet/client/dto/request/CreateReservationDTO.java (2)
api-owner/src/main/java/com/wellmeet/client/dto/AvailableDateDTO.java (1)
Getter(10-22)api-owner/src/main/java/com/wellmeet/client/dto/ReservationDTO.java (1)
Getter(9-24)
api-owner/src/main/java/com/wellmeet/client/dto/request/MemberIdsRequest.java (2)
api-owner/src/main/java/com/wellmeet/client/dto/MemberDTO.java (1)
Getter(8-23)api-owner/src/main/java/com/wellmeet/client/dto/request/CreateReservationDTO.java (1)
Getter(8-19)
api-owner/src/test/java/com/wellmeet/restaurant/RestaurantControllerTest.java (1)
api-owner/src/test/java/com/wellmeet/restaurant/RestaurantServiceTest.java (3)
Nested(43-59)Nested(61-100)Nested(102-168)
api-owner/src/main/java/com/wellmeet/client/dto/RestaurantDTO.java (1)
api-owner/src/main/java/com/wellmeet/client/dto/request/UpdateRestaurantDTO.java (1)
Getter(8-19)
api-owner/src/main/java/com/wellmeet/reservation/dto/ReservationResponse.java (2)
api-owner/src/main/java/com/wellmeet/restaurant/dto/OperatingHoursResponse.java (3)
Getter(10-53)Getter(22-39)Getter(41-52)api-owner/src/main/java/com/wellmeet/global/event/event/ReservationConfirmedEvent.java (1)
Getter(7-33)
api-owner/src/main/java/com/wellmeet/client/dto/MemberDTO.java (1)
api-owner/src/main/java/com/wellmeet/client/dto/OwnerDTO.java (1)
Getter(8-19)
⏰ 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 (32)
api-user/src/main/resources/application-local.yml (1)
5-6: 인프라 레벨 설정 임포트 추가 승인 — 마이그레이션 완료도 확인 필요Feign/Eureka 기반 마이크로서비스 아키텍처로의 전환을 반영하여 인프라 레벨의 Redis/Kafka 설정을 추가한 점은 올바릅니다. 다만 다음 사항을 확인해주세요:
application-domain-local.yml유지 여부: 4번 줄에서 여전히 도메인 레벨 설정을 임포트 중입니다. 이는 의도적인 것인지, 아니면 마이그레이션 중에 누락된 것인지 확인이 필요합니다. api-user가 아직 내부 도메인 모듈에 의존해야 한다면 현재 상태가 맞지만, 그렇지 않다면 제거를 고려하세요.Eureka 클라이언트 설정 확인: PR 요약에 Eureka 서비스 디스커버리가 포함되어 있으나, 이 파일에는 Eureka 설정이 보이지 않습니다. Eureka 설정이 다른 파일에서 로드되는지 확인하고, 필요하면 추가하세요.
환경 일관성: dev/test 환경의 application-dev.yml, application-test.yml도 동일한 방식으로 업데이트되었는지 확인하세요.
api-user/src/main/resources/application-test.yml (1)
5-6: 테스트 프로필 설정 마이그레이션 확인됨infra 설정 파일 검증 완료:
application-infra-redis-test.yml및application-infra-kafka-test.yml모두 존재 (./infra-kafka/src/main/resources/, ./infra-redis/src/main/resources/)- api-user, batch-reminder, api-owner 등 여러 모듈에서 일관되게 새로운 infra 설정 파일을 참조 중
- 마이크로서비스 아키텍처 전환 목표와 정렬
변경사항이 올바르게 적용되었습니다.
batch-reminder/src/main/resources/application-local.yml (1)
5-5: 일관성 있는 변경입니다.
application-test.yml과 동일한 패턴으로 Kafka 구성 import 경로를 업데이트하셨습니다. 로컬 환경에서도 infra 레벨의 Kafka 설정을 사용하도록 변경되었네요. 위에서 제시한 검증 스크립트로 구성 파일 존재 여부를 확인해주세요.batch-reminder/src/main/resources/application-dev.yml (1)
6-6: 세 가지 환경 설정 파일에서 일관된 마이그레이션이 이루어졌습니다.
application-test.yml,application-local.yml과 동일하게 개발 환경에서도 Kafka import 경로를 인프라 레벨로 변경하셨습니다. 세 파일 모두 동일한 패턴을 따르고 있어 일관성이 좋습니다. 다만 프로덕션 환경에 대한 설정 파일이 있다면 유사한 업데이트가 필요한지 확인해주세요.api-user/src/main/resources/application-dev.yml (1)
6-7: 설정 파일 마이그레이션 검증 완료 - 변경사항 정상입니다.코드베이스 전체에서 다음을 확인했습니다:
- ✓ 새로운 설정 파일 존재 확인:
application-infra-redis-dev.yml,application-infra-kafka-dev.yml- ✓ 기존 설정 파일 제거 확인: 구식 파일명 참조 없음
- ✓ 모든 환경 프로필 일관성 확인: dev, local, test 모두
infra-접두사로 정상 변경됨- ✓ 코드베이스 전역 검사: 구식 설정 파일명 참조 0건
변경사항은 완벽하게 구현되었으며, 추가 수정이 필요하지 않습니다.
api-owner/src/main/java/com/wellmeet/client/dto/request/CreateReservationDTO.java (1)
8-12: CreateReservationDTO는 현재 코드베이스에서 사용되지 않습니다. 리뷰 의견이 잘못되었습니다.CreateReservationDTO는 어떤 곳에서도 임포트되거나 참조되지 않으며, ReservationClient 인터페이스에도 이 DTO를 사용하는 메서드가 없습니다.
실제로 사용 중인 request DTO는
api-user모듈의CreateReservationRequest이며, 이미restaurantId와availableDateId필드에@NotNull검증 어노테이션이 포함되어 있습니다. 따라서 request 검증 관련 우려사항은 이미 해결되어 있습니다.Likely an incorrect or invalid review comment.
api-owner/src/main/java/com/wellmeet/restaurant/dto/ReservationStatus.java (1)
3-8: LGTM!예약 상태를 나타내는 enum이 명확하게 정의되어 있습니다. 세 가지 상태(PENDING, CONFIRMED, CANCELED)가 적절합니다.
api-owner/src/main/java/com/wellmeet/client/dto/AvailableDateDTO.java (1)
10-22: LGTM!Feign 클라이언트를 통한 데이터 전송을 위한 DTO가 올바르게 정의되었습니다. Lombok 어노테이션이 적절하게 사용되었습니다.
api-owner/src/main/resources/application-test.yml (1)
2-23: LGTM!테스트 환경에서 서비스 디스커버리와 Eureka 클라이언트를 비활성화하고 DataSource 자동 구성을 제외하는 것이 적절합니다. 이는 테스트 격리와 안정성을 보장합니다.
api-owner/src/main/resources/application-dev.yml (1)
1-28: LGTM!개발 환경 설정이 적절합니다. Eureka 클라이언트와 Feign 설정이 올바르게 구성되었으며, 타임아웃 값(5초)과 로그 레벨(BASIC)이 개발 환경에 적합합니다.
api-owner/src/main/resources/application-local.yml (1)
1-27: LGTM!로컬 환경 설정이 개발 환경과 일관성 있게 구성되었습니다. Eureka 및 Feign 클라이언트 설정이 올바르게 추가되었습니다.
api-owner/src/main/java/com/wellmeet/client/dto/OwnerDTO.java (1)
8-19: LGTM!Owner 데이터 전송을 위한 DTO가 올바르게 정의되었습니다. Lombok 어노테이션과 필드 구조가 프로젝트의 다른 DTO(MemberDTO)와 일관성 있게 구성되었습니다.
api-owner/src/main/java/com/wellmeet/client/dto/request/MemberIdsRequest.java (1)
9-16: LGTM!여러 회원 정보를 조회하기 위한 요청 DTO가 올바르게 정의되었습니다. Lombok 어노테이션과 필드 구조가 적절합니다.
api-owner/src/main/java/com/wellmeet/restaurant/dto/UpdateOperatingHoursRequest.java (1)
3-3: LGTM! BFF 전환에 맞춰 도메인 엔티티 의존성을 제거했습니다.도메인 모듈의 엔티티 대신 DTO 패키지의
DayOfWeek를 사용하도록 변경하여 BFF 아키텍처 목표에 부합합니다.api-owner/src/test/java/com/wellmeet/reservation/ReservationControllerTest.java (1)
20-46: LGTM! 통합 테스트에서 목 기반 테스트로 성공적으로 전환했습니다.
@MockitoBean을 사용하여ReservationService를 목으로 대체하고, 컨트롤러 레이어만 집중적으로 테스트하는 구조로 개선되었습니다. 이는 테스트 속도를 향상시키고 외부 의존성을 제거합니다.api-owner/src/main/java/com/wellmeet/client/OwnerClient.java (1)
8-12: LGTM! Feign 클라이언트가 올바르게 정의되었습니다.
OwnerClient는domain-owner-service와 통신하기 위한 표준적인 Feign 클라이언트 인터페이스입니다. 서비스 디스커버리를 통해 동적으로 엔드포인트를 찾을 수 있습니다.api-owner/src/main/java/com/wellmeet/reservation/dto/DayOfWeek.java (1)
3-13: 검토 의견의 가정이 실제 코드와 맞지 않습니다.검증 결과:
- HOLIDAY 정의 일관성: 두
DayOfWeekenum 모두 HOLIDAY를 포함하고 있으므로 일관됨- domain-reservation-service:
DayOfWeek를 사용하지 않음 (import, 참조 없음)- 서비스 간 연동: enum 변환기/매퍼가 없으며, 각 서비스가 독립적으로 자체 enum 사용
- HOLIDAY 처리 로직: 코드베이스 전체에서 HOLIDAY를 실제로 처리하는 비즈니스 로직 미발견
현재 HOLIDAY는 enum 정의 수준에서만 존재하며 실제 크로스 서비스 일관성 검증이 필요한 구조가 아닙니다.
Likely an incorrect or invalid review comment.
api-owner/src/main/java/com/wellmeet/client/dto/RestaurantDTO.java (1)
12-21: DTO 구조가 적절합니다.Lombok 어노테이션과 필드 정의가 올바르게 구성되어 있습니다. 이 DTO가 응답 전용이라면 현재 구조가 적합합니다.
참고: 만약 이 DTO가 입력 검증에도 사용된다면
@NotNull,@NotBlank등의 validation 어노테이션 추가를 고려하세요. 하지만 현재 용도(Feign 클라이언트 응답)에는 적합합니다.api-owner/src/main/java/com/wellmeet/client/MemberClient.java (1)
12-20: Feign 클라이언트 정의가 적절합니다.서비스 디스커버리(Eureka)를 사용하는 Feign 클라이언트 구조가 올바르게 정의되어 있습니다. 배치 조회 엔드포인트가 POST를 사용하는 것도 요청 본문에 ID 목록을 전달하기에 적합합니다.
참고:
domain-member-service라는 서비스 이름이 Eureka에 등록된 실제 서비스 이름과 일치하는지 확인하세요.api-owner/src/main/java/com/wellmeet/client/dto/request/UpdateRestaurantDTO.java (1)
12-19: 업데이트 DTO 구조가 적절합니다.업데이트 가능한 필드만 포함하고 있으며(id와 ownerId는 제외), Lombok 어노테이션을 통한 보일러플레이트 코드 제거가 잘 되어 있습니다. Feign 클라이언트 내부 통신용 DTO로 적합합니다.
api-owner/src/test/java/com/wellmeet/restaurant/RestaurantServiceTest.java (2)
31-42: 테스트 구조가 우수합니다.Mockito를 사용한 단위 테스트 구조가 잘 설계되어 있습니다:
- Feign 클라이언트를 올바르게 모킹
@InjectMocks로 테스트 대상 서비스 주입- 의존성을 명확하게 분리
139-167: 이벤트 발행 검증이 잘 구현되어 있습니다.식당 업데이트 시
RestaurantUpdatedEvent발행을 올바르게 검증하고 있습니다. 이는 이벤트 기반 아키텍처에서 중요한 테스트 케이스입니다.api-owner/src/main/java/com/wellmeet/restaurant/RestaurantService.java (1)
57-71: 이벤트 리스너 구현이 적절하게 트랜잭션 경계를 처리하고 있습니다.RestaurantEventListener는
@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)를 사용하고 있어, 이벤트는 트랜잭션 커밋 후에만 발행됩니다. 리스너의handleRestaurantUpdate()메서드는 Redis 캐시 무효화(publish 작업)만 수행하며 데이터베이스 작업을 하지 않으므로, 이벤트 발행 실패가 발생해도 원격 업데이트는 이미 완료되어 데이터 불일치가 실질적으로 발생하지 않습니다. 현재 구현은 적절한 패턴입니다.api-owner/src/main/java/com/wellmeet/global/event/event/ReservationConfirmedEvent.java (1)
21-32: 리뷰 의견이 부정확합니다. ReservationDTO는 이미 문자열 기반 상태를 사용하고 있습니다.검증 결과:
ReservationDTO.getStatus()는String타입을 반환합니다 (Enum이 아님)ReservationConfirmedEvent의status필드는 올바르게String으로 선언되어 있습니다- 라인 27의
this.status = reservation.getStatus();할당은 타입이 일치합니다추가적으로
ReservationEventListener의handleReservationConfirmed()메서드는 상태 필드를 사용하지 않으므로, 상태 값 검증이 필요하지 않습니다.ReservationUpdatedEvent에서.name()을 사용하는 것은 다른 도메인 모델 객체(DTO가 아님)를 다루기 때문이며, 이는 별개의 맥락입니다.Likely an incorrect or invalid review comment.
api-owner/src/main/java/com/wellmeet/client/dto/ReservationDTO.java (1)
15-23: ReservationDTO 필드 구성 확인원격 응답 스펙과 일치하는 필드 구성이며 Lombok 적용으로 보일러플레이트가 제거되었습니다. 추가 조치 필요 없습니다.
api-owner/src/main/java/com/wellmeet/client/ReservationClient.java (1)
13-20: Feign 엔드포인트 정의 확인요청 경로와 HTTP 메서드가 도메인 예약 서비스 사양과 정합하며 DTO 도입 흐름과 잘 맞습니다.
api-owner/src/main/java/com/wellmeet/restaurant/dto/OperatingHoursResponse.java (1)
16-51: DTO 기반 변환 로직 확인
BusinessHourDTO를 DTO 응답으로 매핑하는 흐름이 간결하며 기존 필드 구조를 유지하고 있습니다. 추가 수정 사항 없습니다.api-owner/src/main/java/com/wellmeet/reservation/dto/ReservationResponse.java (1)
28-45: ReservationResponse 생성자 리팩터링 확인DTO 기반으로 필드 채우기 흐름이 명확하며
ReservationStatus.valueOf로 상태를 일관되게 enum 으로 변환하는 것도 적절합니다.api-owner/src/test/java/com/wellmeet/reservation/ReservationServiceTest.java (3)
31-38: Mock 선언이 Feign 클라이언트로 올바르게 마이그레이션되었습니다.도메인 서비스에서 Feign 클라이언트로의 전환이 정확하게 이루어졌습니다.
78-106: 예약 확정 테스트가 올바르게 구현되었습니다.Feign 클라이언트와 이벤트 발행을 모두 올바르게 검증하고 있습니다.
109-160: DTO 생성 헬퍼 메서드가 잘 구현되었습니다.빌더 패턴을 일관되게 사용하여 테스트 데이터를 생성하고 있으며, 가독성이 우수합니다.
api-owner/src/main/java/com/wellmeet/client/RestaurantClient.java (1)
15-41: Feign 설정이 이미 적절히 구성되어 있습니다.검증 결과, 다음 항목들이 확인되었습니다:
- FeignErrorDecoder:
api-owner/src/main/java/com/wellmeet/config/FeignConfig.java에서 전역 설정으로 등록됨 ✅- 타임아웃: Connect/Read 각 5초로
Request.Options에서 설정됨 ✅- Retry 메커니즘: 3회 재시도, 100ms 초기 대기, 1000ms 최대 대기로 구성됨 ✅
Circuit breaker는 현재 구현되지 않았으나,
claudedocs/microservices-migration-plan.md에 Resilience4j 추가가 향후 권장사항으로 기록되어 있습니다. RestaurantClient 인터페이스의 구조와 메서드 시그니처는 정확하게 정의되어 있습니다.
| implementation 'org.springframework.cloud:spring-cloud-starter-openfeign' | ||
| implementation 'org.springframework.cloud:spring-cloud-starter-netflix-eureka-client' |
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
도메인 모듈에서 Feign 클라이언트로의 마이그레이션 확인 필요
Spring Cloud OpenFeign과 Eureka 클라이언트가 추가되고 도메인 모듈 의존성이 제거되었습니다. 이는 BFF 아키텍처로의 중요한 전환입니다. 모든 도메인 모듈 사용처가 Feign 클라이언트로 제대로 마이그레이션되었는지 확인이 필요합니다.
다음 스크립트를 실행하여 제거된 도메인 모듈의 import 문이 남아있는지 확인하세요:
🏁 Script executed:
#!/bin/bash
# Description: 도메인 모듈 import가 남아있는지 확인
# 제거된 도메인 모듈 패키지의 import 확인
rg -n --type=java "import com\.wellmeet\.(reservation|member|owner|restaurant)\.(?!dto)" api-owner/src/Length of output: 332
🏁 Script executed:
#!/bin/bash
# Description: 제거된 도메인 모듈의 import 문 확인
# --pcre2 플래그를 사용하여 음의 전방탐색을 지원하도록 수정
rg -n --type=java --pcre2 "import com\.wellmeet\.(reservation|member|owner|restaurant)\.(?!dto)" api-owner/src/Length of output: 293
🏁 Script executed:
#!/bin/bash
# Description: 도메인 모듈 import 확인 (대체 방법 - 더 간단한 패턴)
# 각 도메인 모듈별로 검색 (dto 제외)
echo "=== reservation 모듈 import 확인 ==="
rg -n --type=java "import com\.wellmeet\.reservation\." api-owner/src/ | grep -v "\.dto\."
echo "=== member 모듈 import 확인 ==="
rg -n --type=java "import com\.wellmeet\.member\." api-owner/src/ | grep -v "\.dto\."
echo "=== owner 모듈 import 확인 ==="
rg -n --type=java "import com\.wellmeet\.owner\." api-owner/src/ | grep -v "\.dto\."
echo "=== restaurant 모듈 import 확인 ==="
rg -n --type=java "import com\.wellmeet\.restaurant\." api-owner/src/ | grep -v "\.dto\."Length of output: 413
🏁 Script executed:
#!/bin/bash
# Description: RestaurantRedisService 사용 현황 확인
# RestaurantRedisService 사용 위치 확인
echo "=== RestaurantRedisService 사용 확인 ==="
rg -n --type=java "RestaurantRedisService" api-owner/src/
# RestaurantEventListener.java 파일 내용 확인
echo ""
echo "=== RestaurantEventListener.java 전체 내용 ==="
cat api-owner/src/main/java/com/wellmeet/global/event/listener/RestaurantEventListener.javaLength of output: 1450
도메인 모듈 의존성 제거 후 RestaurantRedisService import 미정리
도메인 모듈 의존성이 build.gradle에서 제거되었지만, 여전히 도메인 모듈의 RestaurantRedisService를 직접 import하고 있습니다. 이는 런타임 에러를 야기할 수 있습니다.
api-owner/src/main/java/com/wellmeet/global/event/listener/RestaurantEventListener.java:4-import com.wellmeet.restaurant.RestaurantRedisService;api-owner/src/test/java/com/wellmeet/global/event/listener/RestaurantEventListenerTest.java:6- 테스트 코드도 동일
Feign 클라이언트로 마이그레이션하거나 도메인 모듈 의존성을 유지해야 합니다.
🤖 Prompt for AI Agents
In api-owner/build.gradle around lines 6-7 and in the Java files
api-owner/src/main/java/com/wellmeet/global/event/listener/RestaurantEventListener.java
(line 4) and
api-owner/src/test/java/com/wellmeet/global/event/listener/RestaurantEventListenerTest.java
(line 6), the project removed the domain module dependency but the code still
imports com.wellmeet.restaurant.RestaurantRedisService; remove that direct
domain import and either (a) re-add the domain module dependency to build.gradle
if you intend to keep using RestaurantRedisService directly, or (b) refactor to
a Feign client or an interface provided by api-owner: define a
RestaurantRedisService client interface, implement/annotate it as a Feign client
or adapter, update the production and test code to inject the client/interface
instead of the domain class, and remove the stale imports from both the listener
and its test.
| import org.springframework.web.bind.annotation.PutMapping; | ||
| import org.springframework.web.bind.annotation.RequestBody; | ||
|
|
||
| @FeignClient(name = "domain-restaurant-service") |
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
서비스 이름이 Eureka에 등록된 이름과 일치하는지 확인하세요.
domain-restaurant-service가 실제 Eureka 서비스 레지스트리에 등록된 서비스 이름과 정확히 일치하는지 검증이 필요합니다.
다음 스크립트로 서비스 이름 사용을 확인할 수 있습니다:
🏁 Script executed:
#!/bin/bash
# Description: Verify service name consistency across Feign clients
# Find all FeignClient annotations and their service names
rg -n '@FeignClient\s*\(\s*name\s*=\s*"[^"]+"\s*\)' -A 1
# Check application properties for service discovery configuration
fd -e yml -e yaml -e properties | xargs rg -n "spring\.application\.name|eureka"Length of output: 3991
🏁 Script executed:
#!/bin/bash
# Extract spring.application.name from each domain service configuration
echo "=== domain-restaurant ==="
rg -n "spring:\s*application:\s*name" -A 1 domain-restaurant/src/main/resources/application.yml
echo "=== domain-reservation ==="
rg -n "spring:\s*application:\s*name" -A 1 domain-reservation/src/main/resources/application.yml
echo "=== domain-owner ==="
rg -n "spring:\s*application:\s*name" -A 1 domain-owner/src/main/resources/application.yml
echo "=== domain-member ==="
rg -n "spring:\s*application:\s*name" -A 1 domain-member/src/main/resources/application.ymlLength of output: 95
🏁 Script executed:
#!/bin/bash
# Extract application names from YAML files using simpler pattern
echo "=== domain-restaurant application.yml ==="
rg -n "application:" -A 5 domain-restaurant/src/main/resources/application.yml | head -20
echo ""
echo "=== domain-reservation application.yml ==="
rg -n "application:" -A 5 domain-reservation/src/main/resources/application.yml | head -20
echo ""
echo "=== domain-owner application.yml ==="
rg -n "application:" -A 5 domain-owner/src/main/resources/application.yml | head -20
echo ""
echo "=== domain-member application.yml ==="
rg -n "application:" -A 5 domain-member/src/main/resources/application.yml | head -20Length of output: 914
RestaurantClient의 @FeignClient 이름이 Eureka 등록명과 일치하지 않습니다.
api-owner/src/main/java/com/wellmeet/client/RestaurantClient.java:15의 @FeignClient(name = "domain-restaurant-service")는 실제 Eureka에 등록된 restaurant-service와 불일치합니다. 이는 서비스 디스커버리 실패를 야기합니다.
수정 필요:
@FeignClient(name = "domain-restaurant-service")→@FeignClient(name = "restaurant-service")
(다른 클라이언트들은 올바르게 구성됨: ReservationClient, OwnerClient, MemberClient)
🤖 Prompt for AI Agents
In api-owner/src/main/java/com/wellmeet/client/RestaurantClient.java around line
15, the @FeignClient name is set to "domain-restaurant-service" which doesn't
match the Eureka registration "restaurant-service"; update the annotation to use
name = "restaurant-service" so service discovery succeeds, ensuring the rest of
the class and imports remain unchanged.
| @Bean | ||
| public Request.Options requestOptions() { | ||
| return new Request.Options( | ||
| 5000, TimeUnit.MILLISECONDS, // connectTimeout | ||
| 5000, TimeUnit.MILLISECONDS, // readTimeout | ||
| true // followRedirects | ||
| ); | ||
| } | ||
|
|
||
| @Bean | ||
| public Retryer retryer() { | ||
| return new Retryer.Default( | ||
| 100, // period (초기 대기 시간) | ||
| 1000, // maxPeriod (최대 대기 시간) | ||
| 3 // maxAttempts (최대 재시도 횟수) | ||
| ); | ||
| } |
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.
🛠️ Refactor suggestion | 🟠 Major
타임아웃 및 재시도 설정을 외부 설정으로 추출하세요.
현재 타임아웃(5000ms)과 재시도 설정(100ms, 1000ms, 3회)이 하드코딩되어 있습니다. 이는 다음과 같은 문제가 있습니다:
- 환경별(dev, staging, prod) 다른 설정이 필요할 수 있습니다
- 운영 중 조정이 필요한 경우 코드 변경 및 재배포가 필요합니다
- 서비스별로 다른 타임아웃이 필요할 수 있습니다
application.yml에 설정을 외부화하는 것을 권장합니다:
# application.yml
feign:
client:
config:
default:
connectTimeout: 5000
readTimeout: 5000
loggerLevel: BASIC
retryer:
period: 100
maxPeriod: 1000
maxAttempts: 3그리고 @ConfigurationProperties를 사용하여 설정을 주입:
@Configuration
@ConfigurationProperties(prefix = "feign.client.config.default")
public class FeignConfig {
private int connectTimeout = 5000;
private int readTimeout = 5000;
@Bean
public Request.Options requestOptions() {
return new Request.Options(
connectTimeout, TimeUnit.MILLISECONDS,
readTimeout, TimeUnit.MILLISECONDS,
true
);
}
// getters and setters
}🤖 Prompt for AI Agents
In api-owner/src/main/java/com/wellmeet/config/FeignConfig.java around lines
19-35, the connect/read timeouts and Retryer parameters are hardcoded; extract
them to application.yml and bind them with @ConfigurationProperties so they can
be changed per environment without redeploy. Add properties in application.yml
(e.g., feign.client.config.default.connectTimeout/readTimeout and
feign.retryer.period/maxPeriod/maxAttempts), annotate FeignConfig with
@Configuration and
@ConfigurationProperties(prefix="feign.client.config.default") (or a separate
prefix for retryer), add fields with getters/setters for timeouts and for
retryer values (or inject a second properties class for retryer), and modify the
requestOptions() and retryer() beans to use those injected fields instead of
literals; ensure the properties class is registered (enable
@EnableConfigurationProperties if needed) and use appropriate types (long/int)
and TimeUnit conversion.
| @Transactional(readOnly = true) | ||
| public OperatingHoursResponse getOperatingHours(String restaurantId) { | ||
| BusinessHours operatingHours = restaurantDomainService.getOperatingHours(restaurantId); | ||
| return new OperatingHoursResponse(operatingHours); | ||
| List<BusinessHourDTO> businessHours = restaurantClient.getOperatingHours(restaurantId); | ||
| return new OperatingHoursResponse(businessHours); | ||
| } |
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.
🛠️ Refactor suggestion | 🟠 Major
원격 서비스 호출에서 @transactional 어노테이션이 불필요합니다.
이 메서드는 RestaurantClient를 통해 원격 서비스를 호출하기만 하며, 로컬 데이터베이스 작업을 수행하지 않습니다. @Transactional(readOnly = true)는 이 경우 의미가 없고 불필요한 트랜잭션 오버헤드만 발생시킵니다.
- @Transactional(readOnly = true)
public OperatingHoursResponse getOperatingHours(String restaurantId) {🤖 Prompt for AI Agents
In api-owner/src/main/java/com/wellmeet/restaurant/RestaurantService.java around
lines 26 to 30, remove the @Transactional(readOnly = true) annotation from
getOperatingHours because this method only calls the remote RestaurantClient and
performs no local DB work; update the method to be a plain non-transactional
public method (or, if you rely on class-level transaction semantics, explicitly
exclude this method from transactions) and run tests to ensure no
transaction-related behavior is expected.
| @Transactional | ||
| public OperatingHoursResponse updateOperatingHours( | ||
| String restaurantId, | ||
| UpdateOperatingHoursRequest request | ||
| ) { | ||
| Map<DayOfWeek, UpdateOperatingHoursRequest.DayHours> dayHours = request.getOperatingHours() | ||
| List<UpdateOperatingHoursDTO.DayHoursDTO> dayHoursList = request.getOperatingHours() | ||
| .stream() | ||
| .collect(Collectors.toMap(DayHours::getDayOfWeek, dayHour -> dayHour)); | ||
| BusinessHours operatingHours = restaurantDomainService.getOperatingHours(restaurantId); | ||
| operatingHours.getValue() | ||
| .forEach(hour -> updateOperatingHour(hour, dayHours.get(hour.getDayOfWeek()))); | ||
| return new OperatingHoursResponse(operatingHours); | ||
| } | ||
| .map(dayHours -> UpdateOperatingHoursDTO.DayHoursDTO.builder() | ||
| .dayOfWeek(dayHours.getDayOfWeek().name()) | ||
| .isOperating(dayHours.isOperating()) | ||
| .open(dayHours.getOpen()) | ||
| .close(dayHours.getClose()) | ||
| .breakStart(dayHours.getBreakStart()) | ||
| .breakEnd(dayHours.getBreakEnd()) | ||
| .build()) | ||
| .toList(); | ||
|
|
||
| UpdateOperatingHoursDTO updateDTO = UpdateOperatingHoursDTO.builder() | ||
| .operatingHours(dayHoursList) | ||
| .build(); | ||
|
|
||
| private void updateOperatingHour(BusinessHour hour, DayHours dayHours) { | ||
| hour.updateHour( | ||
| dayHours.isOperating(), | ||
| dayHours.getOpen(), | ||
| dayHours.getClose(), | ||
| dayHours.getBreakStart(), | ||
| dayHours.getBreakEnd() | ||
| ); | ||
| List<BusinessHourDTO> businessHours = restaurantClient.updateOperatingHours(restaurantId, updateDTO); | ||
| return new OperatingHoursResponse(businessHours); | ||
| } |
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.
🛠️ Refactor suggestion | 🟠 Major
원격 서비스 호출에서 @transactional 어노테이션을 제거해야 합니다.
Line 68에서 eventPublishService.publishRestaurantUpdatedEvent를 호출하지 않는 한, 이 메서드는 순수하게 원격 서비스 호출만 수행합니다. 로컬 데이터베이스 트랜잭션이 필요하지 않으므로 @Transactional을 제거해야 합니다.
- @Transactional
public OperatingHoursResponse updateOperatingHours(
String restaurantId,
UpdateOperatingHoursRequest request
) {📝 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.
| @Transactional | |
| public OperatingHoursResponse updateOperatingHours( | |
| String restaurantId, | |
| UpdateOperatingHoursRequest request | |
| ) { | |
| Map<DayOfWeek, UpdateOperatingHoursRequest.DayHours> dayHours = request.getOperatingHours() | |
| List<UpdateOperatingHoursDTO.DayHoursDTO> dayHoursList = request.getOperatingHours() | |
| .stream() | |
| .collect(Collectors.toMap(DayHours::getDayOfWeek, dayHour -> dayHour)); | |
| BusinessHours operatingHours = restaurantDomainService.getOperatingHours(restaurantId); | |
| operatingHours.getValue() | |
| .forEach(hour -> updateOperatingHour(hour, dayHours.get(hour.getDayOfWeek()))); | |
| return new OperatingHoursResponse(operatingHours); | |
| } | |
| .map(dayHours -> UpdateOperatingHoursDTO.DayHoursDTO.builder() | |
| .dayOfWeek(dayHours.getDayOfWeek().name()) | |
| .isOperating(dayHours.isOperating()) | |
| .open(dayHours.getOpen()) | |
| .close(dayHours.getClose()) | |
| .breakStart(dayHours.getBreakStart()) | |
| .breakEnd(dayHours.getBreakEnd()) | |
| .build()) | |
| .toList(); | |
| UpdateOperatingHoursDTO updateDTO = UpdateOperatingHoursDTO.builder() | |
| .operatingHours(dayHoursList) | |
| .build(); | |
| private void updateOperatingHour(BusinessHour hour, DayHours dayHours) { | |
| hour.updateHour( | |
| dayHours.isOperating(), | |
| dayHours.getOpen(), | |
| dayHours.getClose(), | |
| dayHours.getBreakStart(), | |
| dayHours.getBreakEnd() | |
| ); | |
| List<BusinessHourDTO> businessHours = restaurantClient.updateOperatingHours(restaurantId, updateDTO); | |
| return new OperatingHoursResponse(businessHours); | |
| } | |
| public OperatingHoursResponse updateOperatingHours( | |
| String restaurantId, | |
| UpdateOperatingHoursRequest request | |
| ) { | |
| List<UpdateOperatingHoursDTO.DayHoursDTO> dayHoursList = request.getOperatingHours() | |
| .stream() | |
| .map(dayHours -> UpdateOperatingHoursDTO.DayHoursDTO.builder() | |
| .dayOfWeek(dayHours.getDayOfWeek().name()) | |
| .isOperating(dayHours.isOperating()) | |
| .open(dayHours.getOpen()) | |
| .close(dayHours.getClose()) | |
| .breakStart(dayHours.getBreakStart()) | |
| .breakEnd(dayHours.getBreakEnd()) | |
| .build()) | |
| .toList(); | |
| UpdateOperatingHoursDTO updateDTO = UpdateOperatingHoursDTO.builder() | |
| .operatingHours(dayHoursList) | |
| .build(); | |
| List<BusinessHourDTO> businessHours = restaurantClient.updateOperatingHours(restaurantId, updateDTO); | |
| return new OperatingHoursResponse(businessHours); | |
| } |
🤖 Prompt for AI Agents
In api-owner/src/main/java/com/wellmeet/restaurant/RestaurantService.java around
lines 32 to 55, the method updateOperatingHours is annotated with @Transactional
but only performs a remote service call and no local DB work (and does not call
eventPublishService.publishRestaurantUpdatedEvent), so remove the @Transactional
annotation from this method; also remove the now-unused import for
org.springframework.transaction.annotation.Transactional if present and keep
transactional scope only on methods that perform local DB updates or publish
events.
| assertThat(response.getOperatingHours()).hasSize(3); | ||
| assertThat(response.getOperatingHours().get(0).getDayOfWeek()).isEqualTo(DayOfWeek.MONDAY); | ||
| assertThat(response.getOperatingHours().get(0).isOperating()).isTrue(); | ||
| assertThat(response.getOperatingHours().get(0).getOpen()).isEqualTo("09:00"); | ||
| assertThat(response.getOperatingHours().get(0).getClose()).isEqualTo("21:00"); | ||
| } |
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.
LocalTime 비교를 문자열로 수행하면 테스트가 항상 실패합니다
getOpen()과 getClose()는 LocalTime을 반환합니다. 현재처럼 문자열과 비교하면 equals가 false라 테스트가 무조건 실패합니다. LocalTime.of(...)나 LocalTime.parse(...)를 사용해 동일 타입끼리 비교해 주세요.
- assertThat(response.getOperatingHours().get(0).getOpen()).isEqualTo("09:00");
- assertThat(response.getOperatingHours().get(0).getClose()).isEqualTo("21:00");
+ assertThat(response.getOperatingHours().get(0).getOpen()).isEqualTo(LocalTime.of(9, 0));
+ assertThat(response.getOperatingHours().get(0).getClose()).isEqualTo(LocalTime.of(21, 0));📝 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.
| assertThat(response.getOperatingHours()).hasSize(3); | |
| assertThat(response.getOperatingHours().get(0).getDayOfWeek()).isEqualTo(DayOfWeek.MONDAY); | |
| assertThat(response.getOperatingHours().get(0).isOperating()).isTrue(); | |
| assertThat(response.getOperatingHours().get(0).getOpen()).isEqualTo("09:00"); | |
| assertThat(response.getOperatingHours().get(0).getClose()).isEqualTo("21:00"); | |
| } | |
| assertThat(response.getOperatingHours()).hasSize(3); | |
| assertThat(response.getOperatingHours().get(0).getDayOfWeek()).isEqualTo(DayOfWeek.MONDAY); | |
| assertThat(response.getOperatingHours().get(0).isOperating()).isTrue(); | |
| assertThat(response.getOperatingHours().get(0).getOpen()).isEqualTo(LocalTime.of(9, 0)); | |
| assertThat(response.getOperatingHours().get(0).getClose()).isEqualTo(LocalTime.of(21, 0)); | |
| } |
🤖 Prompt for AI Agents
In api-owner/src/test/java/com/wellmeet/restaurant/RestaurantControllerTest.java
around lines 83 to 88, the test compares LocalTime-returning methods
getOpen()/getClose() to string literals which always fails; change the
assertions to compare LocalTime instances instead (e.g., LocalTime.of(9,0) or
LocalTime.parse("09:00")) and ensure necessary import (java.time.LocalTime) is
present so the types match.
| import: | ||
| - classpath:application-domain-test.yml | ||
| - classpath:application-kafka-test.yml | ||
| - classpath:application-infra-kafka-test.yml |
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
새로운 인프라 Kafka 구성 파일이 실제로 존재하는지 확인이 필요합니다.
마이크로서비스 아키텍처 전환에 맞춰 Kafka 구성 import 경로를 도메인 레벨에서 인프라 레벨로 변경하셨네요. 그러나 application-infra-kafka-test.yml 파일이 실제로 존재하고 올바르게 구성되어 있는지 확인해주세요.
🏁 Script executed:
#!/bin/bash
# Description: Verify that all new infra Kafka config files exist
# Check for test profile
test -f "batch-reminder/src/main/resources/application-infra-kafka-test.yml" && echo "✓ application-infra-kafka-test.yml exists" || echo "✗ application-infra-kafka-test.yml missing"
# Check for local profile
test -f "batch-reminder/src/main/resources/application-infra-kafka-local.yml" && echo "✓ application-infra-kafka-local.yml exists" || echo "✗ application-infra-kafka-local.yml missing"
# Check for dev profile
test -f "batch-reminder/src/main/resources/application-infra-kafka-dev.yml" && echo "✓ application-infra-kafka-dev.yml exists" || echo "✗ application-infra-kafka-dev.yml missing"
# Check if old domain-level configs still exist (might cause confusion)
test -f "batch-reminder/src/main/resources/application-kafka-test.yml" && echo "⚠ Old application-kafka-test.yml still exists" || echo "✓ Old application-kafka-test.yml removed"
test -f "batch-reminder/src/main/resources/application-kafka-local.yml" && echo "⚠ Old application-kafka-local.yml still exists" || echo "✓ Old application-kafka-local.yml removed"
test -f "batch-reminder/src/main/resources/application-kafka-dev.yml" && echo "⚠ Old application-kafka-dev.yml still exists" || echo "✓ Old application-kafka-dev.yml removed"
# Check for production profile configs that might also need updates
test -f "batch-reminder/src/main/resources/application-prod.yml" && echo "Found application-prod.yml - verify if it needs similar updates" || echo "✓ No application-prod.yml found"Length of output: 283
필수 인프라 Kafka 설정 파일이 누락되어 있습니다.
검증 결과, application-test.yml의 Line 5에서 import하는 application-infra-kafka-test.yml 파일이 실제로 존재하지 않습니다. 기존 도메인 레벨의 설정 파일(application-kafka-test.yml)은 제거되었으나, 새로운 인프라 레벨의 설정 파일이 생성되지 않았습니다.
batch-reminder/src/main/resources/application-infra-kafka-test.yml파일을 생성해야 합니다.- 필요시
application-infra-kafka-local.yml,application-infra-kafka-dev.yml도 함께 생성하세요.
이 파일들이 없으면 애플리케이션 시작 시 설정 로드 실패로 인해 런타임 오류가 발생합니다.
🤖 Prompt for AI Agents
In batch-reminder/src/main/resources/application-test.yml around line 5, the
imported file application-infra-kafka-test.yml is missing; create
batch-reminder/src/main/resources/application-infra-kafka-test.yml with the
necessary Kafka configuration previously in application-kafka-test.yml
(bootstrap servers, topics, consumer/producer properties, and any profiles), and
if you use other environments also add application-infra-kafka-local.yml and
application-infra-kafka-dev.yml with corresponding environment-specific values;
ensure the new files are included in version control and the import path in
application-test.yml matches the new filenames exactly.
🚩 Issue
closed #70
🗣️ 리뷰 요구사항 (선택)
Summary by CodeRabbit
릴리스 노트
New Features
Refactor
Chores
Tests