Skip to content

Conversation

@unifolio0
Copy link
Contributor

@unifolio0 unifolio0 commented Nov 3, 2025

🚩 Issue

closed #70

🗣️ 리뷰 요구사항 (선택)

Summary by CodeRabbit

릴리스 노트

  • New Features

    • 서비스간 원격 통신(Feign 기반 클라이언트) 도입으로 외부 서비스 호출 지원
  • Refactor

    • 내부 구조를 클라이언트/DTO 중심으로 전환하여 서비스 분리 및 통신 흐름 개선
    • 이벤트 발행/처리 흐름 보강
  • Chores

    • 환경 설정 업데이트(서비스명, 포트, Eureka/Feign 설정, 인프라 설정 참조 전환)
  • Tests

    • 테스트 구조 대대적 재정비: 일부 통합 테스트 정리 및 Mockito 기반 단위테스트 추가/갱신

@unifolio0 unifolio0 self-assigned this Nov 3, 2025
@unifolio0 unifolio0 added the feat label Nov 3, 2025
@coderabbitai
Copy link

coderabbitai bot commented Nov 3, 2025

Note

Other AI code review bot(s) detected

CodeRabbit 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

api-owner가 내부 도메인 모듈 의존성을 제거하고 Spring Cloud OpenFeign/Eureka 기반 원격 호출로 전환합니다. Feign 클라이언트와 DTO들이 추가되고 서비스·이벤트·설정·테스트들이 DTO/클라이언트 중심으로 리팩토링되었습니다.

Changes

Cohort / File(s) 변경 요약
빌드 및 의존성
api-owner/build.gradle
내부 domain 모듈 의존성 제거, spring-cloud-starter-openfeign, spring-cloud-starter-netflix-eureka-client 추가; testFixture 의존성 제거.
애플리케이션 진입점
api-owner/src/main/java/com/wellmeet/ApiOwnerApplication.java
@EnableFeignClients 추가.
Feign 클라이언트
api-owner/src/main/java/com/wellmeet/client/MemberClient.java, .../OwnerClient.java, .../ReservationClient.java, .../RestaurantClient.java
각 도메인 서비스용 Feign 인터페이스 신규 추가(원격 엔드포인트 매핑).
클라이언트 DTO
api-owner/src/main/java/com/wellmeet/client/dto/*
MemberDTO, OwnerDTO, ReservationDTO, RestaurantDTO, AvailableDateDTO, BusinessHourDTO 등 DTO 추가.
요청 DTO
api-owner/src/main/java/com/wellmeet/client/dto/request/*
MemberIdsRequest, CreateReservationDTO, UpdateRestaurantDTO, UpdateOperatingHoursDTO 등 추가.
Feign 설정
api-owner/src/main/java/com/wellmeet/config/FeignConfig.java, .../FeignErrorDecoder.java
Feign 로그/타임아웃/재시도 설정 및 ErrorDecoder 구현 추가.
서비스 레이어 리팩토링
api-owner/src/main/java/com/wellmeet/reservation/ReservationService.java, .../restaurant/RestaurantService.java
도메인 서비스 호출을 Feign 클라이언트 호출로 전환; DTO 기반 처리 및 이벤트 페이로드 변경.
응답/도메인 DTO 변환
api-owner/src/main/java/com/wellmeet/reservation/dto/ReservationResponse.java, .../restaurant/dto/OperatingHoursResponse.java
생성자 파라미터·내부 로직을 도메인 엔티티→클라이언트 DTO로 변경, 상태 파싱 업데이트.
열거형·타입 추가
api-owner/src/main/java/com/wellmeet/reservation/dto/DayOfWeek.java, .../restaurant/dto/ReservationStatus.java
DayOfWeek, ReservationStatus enum 추가.
이벤트 변경
api-owner/src/main/java/com/wellmeet/global/event/event/ReservationConfirmedEvent.java
생성자 파라미터를 ReservationReservationDTO로 변경, createdAt 등 필드 초기화 조정.
설정 파일
api-owner/src/main/resources/application-{dev,local,test}.yml
Eureka 및 Feign 기본 설정 추가, spring.application.name 설정, domain 관련 import → infra로 변경, 포트 변경(8087).
테스트 정리/삭제
api-owner/src/test/java/com/wellmeet/BaseControllerTest.java, .../global/event/*, .../restaurant/DomainRestaurant*
테스트용 fixture 주입 및 도메인 기반 테스트 일부 제거/삭제.
테스트 신규/수정
api-owner/src/test/java/com/wellmeet/reservation/*, .../restaurant/*
Mockito/MockBean 기반으로 테스트 재작성(ReservationControllerTest, ReservationServiceTest, RestaurantControllerTest, RestaurantServiceTest 등).
환경 설정 일관화
api-user/**, batch-reminder/**, infra-redis/**
여러 application-*.yml에서 domain → infra로 Redis/Kafka import 경로 변경, 테스트 프로파일명 업데이트.
문서 변경
claudedocs/phase4-detailed-plan.md, claudedocs/phase5-bff-migration-plan.md
Phase4 문서 삭제, Phase5 BFF 마이그레이션 계획 문서 추가.

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
Loading
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
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 분

  • 추가 주의가 필요한 파일:
    • ReservationService.java: 다중 Feign 호출 흐름, 예외·타임아웃·재시도 영향 검증.
    • ReservationResponse.java: DTO→내부 enum 매핑(ReservationStatus.valueOf) 예외 처리 확인.
    • FeignConfig.java / FeignErrorDecoder.java: 타임아웃·재시도·에러 매핑 로직과 로케일(한글 메시지) 확인.
    • 테스트 재작성된 부분: 삭제된 도메인 기반 테스트의 커버리지 보완 여부 확인.

Possibly related PRs

Suggested reviewers

  • ezcolin2

Poem

🐰 한 걸음 뗀 작은 토끼가 말하네,
Feign으로 친구 찾아 떠나고,
DTO 주머니에 소식 담아 전하며,
Eureka 별빛 아래 서비스들 손잡네,
깡총깡총—새로운 BFF 길에 축배를! 🎉

Pre-merge checks and finishing touches

❌ Failed checks (3 inconclusive)
Check name Status Explanation Resolution
Linked Issues Check ❓ Inconclusive 연결된 Issue #70은 "[FEAT] api-owner BFF 전환"이라는 제목만 포함하고 있으며, 구체적인 설명, 요구사항, 또는 수용 기준이 명시되어 있지 않습니다. Raw summary에서 확인할 수 있는 변경사항들(Feign 클라이언트 도입, DTO 기반 구조, 도메인 의존성 제거, 서비스 리팩토링, 테스트 마이그레이션)은 BFF 전환이라는 제목과 개념적으로 일치하나, Issue에 명시된 구체적인 요구사항이 없어 정확한 준수 여부를 판단하기 어렵습니다. Issue #70에 구체적인 요구사항과 수용 기준을 작성하거나, PR 설명에서 Issue의 의도와 구현 결과의 대응 관계를 명시할 것을 권장합니다. 예를 들어, 도메인 의존성 제거 완료, Feign 기반 클라이언트 통합, 서비스 계층 리팩토링, 테스트 마이그레이션 등의 체크리스트를 포함하면 더 명확할 것입니다.
Out of Scope Changes Check ❓ Inconclusive Raw summary를 분석하면, PR 타이틀 "[FEAT] api-owner BFF 전환"은 api-owner 모듈에만 초점을 맞추고 있으나, 실제 변경사항은 api-owner(주요), api-user, batch-reminder, infra-redis, 그리고 claudedocs(문서)에까지 미치고 있습니다. api-user와 batch-reminder의 변경은 설정 파일(application-dev.yml, application-local.yml, application-test.yml)의 import 경로만 변경하고 있으므로(domain-redis/kafka → infra-redis/kafka), 이것이 api-owner BFF 전환의 필수 일부인지 아니면 별도의 인프라 통합 작업인지 불명확합니다. api-owner 외 모듈의 변경사항이 BFF 전환과 어떤 관계가 있는지 명확히 할 것을 권장합니다. 설정 import 변경(infra 패키지 통합)이 api-owner BFF 전환과 함께 필수적으로 수행되어야 하는 사항인지, 아니면 별도 PR로 분리해야 할 사항인지 검토하고 PR 설명에 명시하면 좋을 것 같습니다.
Description Check ❓ Inconclusive PR 설명은 제공된 템플릿의 기본 구조(Issue 섹션과 리뷰 요구사항 섹션)를 따르고 있으나, 실질적인 내용이 극히 부족합니다. Issue #70 참조는 포함되어 있으나, 변경사항의 동기, 구현 범위, 테스트 방법, 또는 검증 기준 등 중요한 정보가 누락되어 있습니다. 선택 섹션인 "리뷰 요구사항"도 완전히 비어있습니다. PR 설명을 더 상세하게 작성할 것을 권장합니다. 변경사항의 주요 목표(도메인 의존성 제거, Feign 클라이언트 기반 아키텍처 도입), 영향받는 모듈(api-owner 서비스 및 관련 설정), 그리고 검증 방법(기존 테스트 마이그레이션, Feign 호출 검증)을 명시하면 리뷰어의 이해도가 높아질 것입니다.
✅ Passed checks (1 passed)
Check name Status Explanation
Title Check ✅ Passed PR 타이틀 "[FEAT] api-owner BFF 전환"은 changeset의 주요 변화를 명확하게 요약합니다. Raw summary에 따르면 api-owner 모듈이 내부 domain 모듈 의존성에서 Spring Cloud OpenFeign 기반의 REST 클라이언트 아키텍처로 전환되었으며, 이는 전형적인 BFF(Backend for Frontend) 패턴 마이그레이션입니다. Feign 클라이언트(MemberClient, OwnerClient, ReservationClient, RestaurantClient), DTO 기반 구조, 그리고 서비스 리팩토링이 모두 이 주제와 일치합니다.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 641695a and 95f75fe.

📒 Files selected for processing (2)
  • infra-redis/src/test/java/com/wellmeet/reservation/ReservationRedisServiceTest.java (2 hunks)
  • infra-redis/src/test/java/com/wellmeet/restaurant/RestaurantRedisServiceTest.java (2 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 (4)
infra-redis/src/test/java/com/wellmeet/reservation/ReservationRedisServiceTest.java (2)

3-5: 명시적 import로의 변경이 적절합니다.

wildcard import에서 명시적 import로 변경하여 코드 가독성이 향상되었습니다. 어떤 assertion 메서드들이 사용되는지 명확하게 표시되어 좋습니다.


20-20: 프로파일 설정이 올바르게 구성되었습니다.

검증 결과, infra-redis-test 프로파일이 infra-redis/src/main/resources/application-infra-redis-test.yml에 정확히 정의되어 있으며, Redis 설정(localhost:6379)이 테스트 환경에 적합하게 구성되어 있습니다. 같은 모듈의 다른 Redis 테스트(RestaurantRedisServiceTest.java)에서도 동일한 프로파일을 사용하고 있어 BFF 마이그레이션 전환이 일관성 있게 적용되었음을 확인할 수 있습니다.

infra-redis/src/test/java/com/wellmeet/restaurant/RestaurantRedisServiceTest.java (2)

3-4: 명시적 임포트로 개선되었습니다.

와일드카드 임포트 대신 실제 사용하는 메서드만 명시적으로 임포트하여 코드 가독성과 의존성을 명확하게 했습니다.


18-18: 프로필 변경이 올바르게 적용되어 있습니다.

application-infra-redis-test.yml 설정 파일이 infra-redis/src/main/resources에 존재하며, 변경된 @ActiveProfiles("infra-redis-test") 어노테이션이 다른 테스트 클래스(ReservationRedisServiceTest.java)에서도 일관되게 사용되고 있습니다. 모듈 구조 개편에 맞게 올바르게 반영되었습니다.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@gemini-code-assist
Copy link

Summary of Changes

Hello @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

  • BFF 아키텍처 전환: api-owner 모듈이 domain-* 프로젝트에 대한 직접적인 의존성을 제거하고, Feign 클라이언트를 통해 각 도메인 서비스와 통신하도록 전환되었습니다. 이는 마이크로서비스 간의 결합도를 낮추고 모듈성을 향상시킵니다.
  • Feign 클라이언트 도입: MemberClient, OwnerClient, ReservationClient, RestaurantClient 등 새로운 Feign 클라이언트 인터페이스와 관련 DTO들이 추가되어, 원격 서비스 호출을 선언적으로 처리할 수 있게 되었습니다.
  • 서비스 계층 리팩토링: ReservationService 및 RestaurantService 내에서 기존의 DomainService 직접 호출이 새로 도입된 Feign 클라이언트 호출로 대체되었으며, 이에 맞춰 DTO 매핑 로직도 업데이트되었습니다.
  • 테스트 코드 마이그레이션: 기존의 domain-* 프로젝트 픽스처를 사용하던 테스트 코드가 Mockito 기반의 Mock 객체를 활용하는 방식으로 전면 재작성되었습니다. 이는 테스트의 격리성을 높이고 실행 속도를 향상시킵니다.
  • 인프라 설정 업데이트: build.gradle에 Spring Cloud OpenFeign 및 Eureka Client 의존성이 추가되었고, application.yml 파일에 Eureka 및 Feign 클라이언트 관련 설정이 반영되었습니다. 또한, infra-kafka 및 infra-redis 관련 설정 파일 이름이 일관성 있게 변경되었습니다.
Using Gemini Code Assist

The 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 /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

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 .gemini/ folder in the base of the repository. Detailed instructions can be found here.

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

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link

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

Choose a reason for hiding this comment

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

Code Review

api-owner 서비스를 BFF 아키텍처로 성공적으로 리팩토링한 점이 인상적입니다. Feign 클라이언트를 도입하여 도메인 모듈에 대한 직접 의존성을 제거하고, Eureka를 통한 서비스 디스커버리를 적용한 것은 마이크로서비스 전환의 좋은 예시입니다. 특히 테스트 전략을 DB 통합 테스트에서 Mock 기반의 단위/통합 테스트로 전환하여 테스트 속도와 안정성을 높인 점이 돋보입니다. 리뷰에서는 Feign 오류 처리 개선, 서비스 간 통신 효율성 증대, 테스트 코드의 유지보수성 향상에 대한 몇 가지 제안을 포함했습니다.

Comment on lines +17 to +23
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);
};

Choose a reason for hiding this comment

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

medium

404 Not Found 에러에 대해 IllegalArgumentException을 던지는 것은 의미상 맞지 않습니다. IllegalArgumentException은 잘못된 인수를 의미하는 반면, 404는 리소스를 찾을 수 없음을 나타냅니다. 이로 인해 글로벌 예외 처리기에서 오류를 잘못 해석할 수 있습니다. ResourceNotFoundException과 같은 사용자 정의 예외를 생성하여 사용하거나, 상황에 더 적합한 기존 예외를 사용하는 것이 좋습니다. 이렇게 하면 오류 상태를 더 명확하게 나타내고 더 나은 오류 처리가 가능해집니다.

Comment on lines +67 to +69
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);

Choose a reason for hiding this comment

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

medium

confirmReservation 메소드에서 예약을 확정한 후(confirmReservation) 다시 예약 정보를 조회(getReservation)하기 위해 두 번의 네트워크 호출이 발생하고 있습니다. ReservationClientconfirmReservation 메소드가 void 대신 업데이트된 ReservationDTO를 반환하도록 수정하면 네트워크 호출을 한 번으로 줄여 성능을 향상시킬 수 있습니다.

이를 위해 ReservationClient 인터페이스의 confirmReservation 메소드 시그니처도 ReservationDTO confirmReservation(@PathVariable("id") Long id); 와 같이 변경해야 합니다.

Suggested change
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);

Comment on lines +1 to +19
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;
}

Choose a reason for hiding this comment

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

medium

CreateReservationDTO 파일이 api-owner 모듈에 추가되었지만, 현재 모듈 내의 어떤 코드에서도 사용되지 않는 것으로 보입니다. 만약 다른 모듈(예: api-user)에서 사용될 예정이라면, 해당 모듈로 이동하거나 공통 client 모듈로 분리하는 것을 고려해 보세요. 사용되지 않는 코드는 혼란을 야기하고 유지보수 비용을 증가시킬 수 있으므로, api-owner 모듈에 필요하지 않다면 제거하는 것이 좋습니다.

Comment on lines +132 to +214
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;
}

Choose a reason for hiding this comment

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

medium

테스트 데이터 생성을 위해 createOperatingHoursResponse, createDayHours 등의 헬퍼 메소드에서 리플렉션을 사용하는 것은 테스트 코드를 이해하기 어렵게 만들고 깨지기 쉽게 만듭니다. OperatingHoursResponseDayHours 클래스에는 BusinessHourDTO를 사용하는 public 생성자가 있으므로, 테스트에서 BusinessHourDTO 객체를 생성하여 이 생성자들을 활용하는 것이 좋습니다. 이렇게 하면 리플렉션 없이 더 깔끔하고 유지보수하기 쉬운 테스트 코드를 작성할 수 있습니다.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 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 필드가 primitive int 타입으로 선언되어 있어 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 접두사 없이 명명되었지만, isVipis 접두사를 사용합니다.

일관성을 위해 다음과 같이 수정하는 것을 권장합니다:

-    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.DayHoursoperating 사용
  • UpdateOperatingHoursDTO.DayHoursDTOisOperating 사용

이로 인해 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와 구조가 중복됩니다.

UpdateOperatingHoursDTOUpdateOperatingHoursRequest.DayHours는 거의 동일한 필드를 가지고 있습니다:

  • 둘 다 운영 시간 업데이트를 위한 유사한 구조
  • 주요 차이점: dayOfWeek 타입 (String vs Enum), isOperating vs operating 필드명

이는 DTO 간 변환 로직이 필요하며, 향후 유지보수 시 두 클래스를 동시에 수정해야 하는 부담이 있습니다.

다음을 고려하세요:

  1. 가능하다면 하나의 DTO로 통합
  2. 또는 명확한 변환 유틸리티 메서드 제공
  3. 두 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

📥 Commits

Reviewing files that changed from the base of the PR and between b4ca8f9 and 641695a.

📒 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.java
  • api-owner/src/test/java/com/wellmeet/restaurant/RestaurantServiceTest.java
  • api-owner/src/main/java/com/wellmeet/restaurant/dto/OperatingHoursResponse.java
  • api-owner/src/main/java/com/wellmeet/restaurant/RestaurantService.java
  • api-owner/src/main/java/com/wellmeet/reservation/ReservationService.java
  • api-owner/src/main/java/com/wellmeet/client/dto/request/UpdateRestaurantDTO.java
  • api-owner/src/test/java/com/wellmeet/reservation/ReservationServiceTest.java
  • api-owner/src/main/java/com/wellmeet/client/RestaurantClient.java
  • api-owner/src/main/java/com/wellmeet/client/ReservationClient.java
  • api-owner/src/test/java/com/wellmeet/restaurant/RestaurantControllerTest.java
  • api-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 설정을 추가한 점은 올바릅니다. 다만 다음 사항을 확인해주세요:

  1. application-domain-local.yml 유지 여부: 4번 줄에서 여전히 도메인 레벨 설정을 임포트 중입니다. 이는 의도적인 것인지, 아니면 마이그레이션 중에 누락된 것인지 확인이 필요합니다. api-user가 아직 내부 도메인 모듈에 의존해야 한다면 현재 상태가 맞지만, 그렇지 않다면 제거를 고려하세요.

  2. Eureka 클라이언트 설정 확인: PR 요약에 Eureka 서비스 디스커버리가 포함되어 있으나, 이 파일에는 Eureka 설정이 보이지 않습니다. Eureka 설정이 다른 파일에서 로드되는지 확인하고, 필요하면 추가하세요.

  3. 환경 일관성: dev/test 환경의 application-dev.yml, application-test.yml도 동일한 방식으로 업데이트되었는지 확인하세요.

api-user/src/main/resources/application-test.yml (1)

5-6: 테스트 프로필 설정 마이그레이션 확인됨

infra 설정 파일 검증 완료:

  • application-infra-redis-test.ymlapplication-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이며, 이미 restaurantIdavailableDateId 필드에 @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 클라이언트가 올바르게 정의되었습니다.

OwnerClientdomain-owner-service와 통신하기 위한 표준적인 Feign 클라이언트 인터페이스입니다. 서비스 디스커버리를 통해 동적으로 엔드포인트를 찾을 수 있습니다.

api-owner/src/main/java/com/wellmeet/reservation/dto/DayOfWeek.java (1)

3-13: 검토 의견의 가정이 실제 코드와 맞지 않습니다.

검증 결과:

  • HOLIDAY 정의 일관성: 두 DayOfWeek enum 모두 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이 아님)
  • ReservationConfirmedEventstatus 필드는 올바르게 String으로 선언되어 있습니다
  • 라인 27의 this.status = reservation.getStatus(); 할당은 타입이 일치합니다

추가적으로 ReservationEventListenerhandleReservationConfirmed() 메서드는 상태 필드를 사용하지 않으므로, 상태 값 검증이 필요하지 않습니다. 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 인터페이스의 구조와 메서드 시그니처는 정확하게 정의되어 있습니다.

Comment on lines +6 to +7
implementation 'org.springframework.cloud:spring-cloud-starter-openfeign'
implementation 'org.springframework.cloud:spring-cloud-starter-netflix-eureka-client'
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 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.java

Length 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")
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 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.yml

Length 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 -20

Length 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.

Comment on lines +19 to +35
@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 (최대 재시도 횟수)
);
}
Copy link

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.

Comment on lines 26 to 30
@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);
}
Copy link

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.

Comment on lines 32 to 55
@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);
}
Copy link

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.

Suggested change
@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.

Comment on lines +83 to +88
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");
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

LocalTime 비교를 문자열로 수행하면 테스트가 항상 실패합니다

getOpen()getClose()LocalTime을 반환합니다. 현재처럼 문자열과 비교하면 equalsfalse라 테스트가 무조건 실패합니다. 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.

Suggested change
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
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 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.

@unifolio0 unifolio0 merged commit a84551e into Team-soft-macaron:develop Nov 3, 2025
2 checks passed
@unifolio0 unifolio0 deleted the feat/#70 branch November 3, 2025 08:18
This was referenced Nov 4, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[FEAT] api-owner BFF 전환

1 participant