-
Notifications
You must be signed in to change notification settings - Fork 2
[FEAT] reservation 서버 분리 #69
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
|
Caution Review failedThe pull request is closed. Note Other AI code review bot(s) detectedCodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review. Walkthrough예약 도메인을 독립 마이크로서비스로 분리하고 domain-reservation 모듈에 REST 컨트롤러, 애플리케이션 서비스, DTO들, Dockerfile·docker-compose·build.gradle·application.yml 등 배포·운영 스캐폴드를 추가했으며 Phase4 마이그레이션 및 BFF/분산 트랜잭션 문서를 포함합니다. Changes
Sequence Diagram(s)sequenceDiagram
autonumber
participant Client as 클라이언트
participant Controller as DomainReservationController
participant AppService as ReservationApplicationService
participant DomainService as ReservationDomainService
participant Repo as Repository
Client->>Controller: POST /api/reservation (CreateReservationRequest)
activate Controller
Controller->>AppService: createReservation(request)
activate AppService
AppService->>DomainService: alreadyReserved(memberId, restaurantId, availableDateId)
activate DomainService
DomainService->>Repo: 중복 조회
Repo-->>DomainService: 중복 여부
deactivate DomainService
alt 중복 없음
AppService->>DomainService: save(new Reservation)
activate DomainService
DomainService->>Repo: save(Reservation)
Repo-->>DomainService: 저장된 Reservation
DomainService-->>AppService: Reservation
deactivate DomainService
AppService->>AppService: ReservationResponse.from(...)
AppService-->>Controller: ReservationResponse (201 Created)
else 중복 있음
AppService-->>Controller: 예외/에러 응답
end
deactivate AppService
Controller-->>Client: 201 Created / 4xx 에러 등
deactivate Controller
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes 주의 집중 권장 항목:
Possibly related PRs
Suggested reviewers
Poem
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
📜 Recent review detailsConfiguration used: CodeRabbit UI Review profile: CHILL Plan: Pro 📒 Files selected for processing (1)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Summary of ChangesHello @unifolio0, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed! 이 Pull Request는 마이크로서비스 전환 계획의 일환으로 예약 도메인 서버를 독립적인 서비스로 분리하는 중요한 단계입니다. Highlights
Using Gemini Code AssistThe full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips. Invoking Gemini You can request assistance from Gemini at any point by creating a comment using either
Customization To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a Limitations & Feedback Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here. You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension. Footnotes
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Code Review
이 Pull Request는 예약 도메인을 별도의 마이크로서비스로 분리하는 중요한 단계입니다. 아키텍처 변경에 대한 상세한 문서 업데이트는 매우 인상적이며, 새로운 domain-reservation 서비스의 구조는 컨트롤러, 애플리케이션 서비스, 도메인 서비스로 잘 분리되어 있습니다. 특히 Dockerfile에서 non-root 사용자를 설정한 점은 보안적으로 훌륭한 결정입니다.
다만, 몇 가지 개선점을 발견했습니다. 문서와 구현 간의 불일치, Docker 설정에서의 보안 및 모범 사례 관련 이슈, 그리고 애플리케이션 서비스 로직의 복잡성 등입니다. 특히 updateReservation 메소드는 리팩토링이 필요해 보이며, createReservation에서는 불필요한 중복 확인으로 인한 경쟁 조건이 존재합니다. 이러한 부분들에 대해 구체적인 리뷰 의견을 남겼습니다.
| @Transactional | ||
| public ReservationResponse updateReservation(Long reservationId, UpdateReservationRequest request) { | ||
| Reservation reservation = reservationDomainService.getById(reservationId); | ||
|
|
||
| if (request.status() != null && reservation.getStatus() != request.status()) { | ||
| if (request.status() == ReservationStatus.CONFIRMED) { | ||
| reservation.confirm(); | ||
| } else if (request.status() == ReservationStatus.CANCELED) { | ||
| reservation.cancel(); | ||
| } | ||
| } | ||
|
|
||
| if (request.partySize() != null || request.specialRequest() != null) { | ||
| Long availableDateId = reservation.getAvailableDateId(); | ||
| int partySize = request.partySize() != null ? request.partySize() : reservation.getPartySize(); | ||
| String specialRequest = request.specialRequest() != null ? request.specialRequest() | ||
| : reservation.getSpecialRequest(); | ||
|
|
||
| reservation.update(availableDateId, partySize, specialRequest); | ||
| } | ||
|
|
||
| Reservation saved = reservationDomainService.save(reservation); | ||
| return ReservationResponse.from(saved); | ||
| } |
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.
updateReservation 메소드의 로직이 복잡하고 이해하기 어렵습니다. 특히 상태 변경 로직과 다른 필드 업데이트 로직이 분리되어 있고, reservation.update() 메소드 호출 방식에 혼란의 여지가 있습니다.
- 상태 변경 로직:
if (request.status() != null && ...)블록은CONFIRMED와CANCELED상태 변경만 처리합니다. 이 로직은Reservation엔티티 내부로 옮겨 캡슐화하는 것이 좋습니다. - 필드 업데이트 로직:
partySize나specialRequest가null이 아닐 때reservation.update()를 호출합니다. 이때availableDateId는 기존 예약의 값을 그대로 사용하므로, 사실상availableDateId는 이 메소드를 통해 변경할 수 없습니다. 이는Reservation.update()메소드의 시그니처와UpdateReservationRequestDTO의 필드 간에 불일치를 만듭니다.
이 메소드를 더 명확하고 견고하게 리팩토링하는 것을 제안합니다. 예를 들어, 엔티티에 상태 변경과 세부 정보 업데이트를 위한 명확한 책임의 메소드를 만들고, 서비스 레이어에서는 이를 호출만 하도록 단순화할 수 있습니다.
@Transactional
public ReservationResponse updateReservation(Long reservationId, UpdateReservationRequest request) {
Reservation reservation = reservationDomainService.getById(reservationId);
// partySize나 specialRequest가 제공된 경우에만 업데이트
if (request.partySize() != null || request.specialRequest() != null) {
// availableDateId는 업데이트하지 않으므로 기존 값을 사용
// partySize와 specialRequest는 null일 수 있으므로 엔티티에서 처리
reservation.updateDetails(
request.partySize(),
request.specialRequest()
);
}
// 상태 변경 요청이 있는 경우 처리
if (request.status() != null) {
reservation.changeStatusTo(request.status());
}
// save는 @Transactional에 의해 관리되므로 명시적으로 호출할 필요가 없을 수 있습니다.
// 하지만 명시적으로 호출하는 것도 괜찮습니다.
Reservation saved = reservationDomainService.save(reservation);
return ReservationResponse.from(saved);
}위 예시처럼 엔티티(Reservation)에 updateDetails와 changeStatusTo 같은 메소드를 만들어 로직을 위임하면 서비스 레이어가 더 깔끔해집니다.
| // 1. BFF가 Redis 분산 락 획득 | ||
| if (!redisService.isReserving(memberId, request.restaurantId(), request.availableDateId())) { | ||
| throw new AlreadyReservingException(); | ||
| } |
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.
BFF의 reserve 메소드 예시 코드에서 redisService.isReserving 메소드 사용법이 실제 구현과 다릅니다.
예시 코드에서는 isReserving이 boolean을 반환하는 것처럼 if (!redisService.isReserving(...)) 형태로 사용되었지만, 실제 ReservationRedisService의 isReserving 메소드는 lock 획득에 실패하면 예외를 던지고 반환 값이 void입니다.
문서의 정확성을 위해 예시 코드를 실제 사용법에 맞게 수정하는 것이 좋겠습니다.
// 수정 제안
public CreateReservationResponse reserve(String memberId, CreateReservationRequest request) {
// 1. BFF가 Redis 분산 락 획득
redisService.isReserving(memberId, request.restaurantId(), request.availableDateId());
try {
// ... 이하 로직
} catch (Exception e) {
// ...
}
}| SPRING_DATASOURCE_PASSWORD: password | ||
| DB_PASSWORD: password |
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.
reservation-service의 환경 변수 설정에 몇 가지 개선점이 있습니다.
- 하드코딩된 비밀번호:
DB_PASSWORD와SPRING_DATASOURCE_PASSWORD가 'password'로 하드코딩되어 있습니다. 보안 강화를 위해.env파일을 사용하여 민감한 정보를 관리하고,.env파일은.gitignore에 추가하는 것을 권장합니다. - 중복 및 비일관적인 변수:
DB_PASSWORD와SPRING_DATASOURCE_PASSWORD두 변수가 모두 암호를 설정하고 있습니다.domain-reservation/application.yml에서는DB_PASSWORD를 사용하므로SPRING_DATASOURCE_PASSWORD는 불필요합니다. 또한 다른 서비스(owner-service등)는SPRING_DATASOURCE_PASSWORD를 사용하므로, 변수명을 통일하여 일관성을 유지하는 것이 좋습니다.
아래와 같이 수정하는 것을 제안합니다.
docker-compose.yml:
...
reservation-service:
...
environment:
SPRING_PROFILES_ACTIVE: docker
SPRING_DATASOURCE_URL: jdbc:mysql://mysql-reservation:3306/wellmeet_reservation
SPRING_DATASOURCE_USERNAME: root
SPRING_DATASOURCE_PASSWORD: ${DB_PASSWORD} # .env 파일에서 읽어오도록 변경
EUREKA_CLIENT_SERVICE_URL_DEFAULTZONE: http://discovery-server:8761/eureka/
...domain-reservation/src/main/resources/application.yml:
...
datasource:
...
password: ${SPRING_DATASOURCE_PASSWORD} # 다른 서비스와 통일
....env (새 파일, .gitignore에 추가):
DB_PASSWORD=your_local_password
|
|
||
| ENV JAVA_OPTS="-Xms512m -Xmx1024m -XX:+UseG1GC -XX:MaxGCPauseMillis=200" | ||
|
|
||
| ENTRYPOINT ["sh", "-c", "java $JAVA_OPTS -jar app.jar --spring.profiles.active=docker"] |
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.
ENTRYPOINT에 --spring.profiles.active=docker 프로필을 하드코딩하는 대신, docker-compose.yml에 정의된 SPRING_PROFILES_ACTIVE 환경 변수를 사용하도록 하는 것이 더 유연한 방법입니다.
현재 docker-compose.yml에도 SPRING_PROFILES_ACTIVE: docker가 설정되어 있어 중복이며, 다른 환경(예: 스테이징)에서 다른 프로필로 이 이미지를 재사용하기 어렵게 만듭니다.
ENTRYPOINT에서 프로필 설정을 제거하여 환경 변수로 제어하도록 변경하는 것을 권장합니다.
ENTRYPOINT ["sh", "-c", "java $JAVA_OPTS -jar app.jar"]
| import org.springframework.web.bind.annotation.RestController; | ||
|
|
||
| @RestController | ||
| @RequestMapping("/domain/reservation") |
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.
컨트롤러의 기본 경로가 /domain/reservation으로 설정되어 있습니다. 이는 마이그레이션 계획 문서(claudedocs/microservices-migration-plan.md, claudedocs/phase4-detailed-plan.md)에 명시된 /api/reservations와 일치하지 않습니다.
문서와 구현 간의 일관성을 유지하고, 향후 Feign 클라이언트에서 호출할 때 혼동을 피하기 위해 경로를 문서와 동일하게 /api/reservations로 변경하거나, 의도된 것이라면 문서를 업데이트하는 것이 좋겠습니다.
| @RequestMapping("/domain/reservation") | |
| @RequestMapping("/api/reservations") |
| public ReservationResponse createReservation(CreateReservationRequest request) { | ||
| reservationDomainService.alreadyReserved( | ||
| request.memberId(), | ||
| request.restaurantId(), | ||
| request.availableDateId() | ||
| ); |
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.
createReservation 메소드에서 reservationDomainService.alreadyReserved()를 호출하여 중복 예약을 확인한 후 reservationDomainService.save()를 호출하고 있습니다. 이는 TOCTOU(Time-of-check to time-of-use) 경쟁 조건을 유발할 수 있습니다.
두 개의 동시 요청이 alreadyReserved() 체크를 통과한 후 save()를 시도하면, 하나는 데이터베이스의 unique 제약 조건 위반으로 실패하게 됩니다. ReservationDomainService.save() 메소드 내에서 이미 DataIntegrityViolationException을 처리하여 ALREADY_RESERVED 예외를 던지도록 구현되어 있으므로, alreadyReserved()를 미리 호출하는 것은 중복이며 불필요합니다.
경쟁 조건을 없애고 코드를 단순화하기 위해 alreadyReserved() 호출을 제거하는 것을 권장합니다.
public ReservationResponse createReservation(CreateReservationRequest request) {There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 4
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
docker-compose.yml (1)
183-208: reservation-service의 프로파일을local로 수정 필요검증 결과, domain-reservation 서비스는
application-domain-local.yml파일을 가지고 있지만,application-domain-docker.yml파일은 존재하지 않습니다.현재 설정:
- reservation-service:
SPRING_PROFILES_ACTIVE: docker(해당 파일 없음)- 다른 모든 서비스:
SPRING_PROFILES_ACTIVE: local(설정 파일 존재)이는 명백한 오류입니다. reservation-service도 다른 서비스들과 동일하게
local프로파일을 사용하도록 수정하세요.SPRING_PROFILES_ACTIVE: local
🧹 Nitpick comments (3)
docker-compose.yml (1)
196-200: 환경 변수 중복 설정
SPRING_DATASOURCE_URL,SPRING_DATASOURCE_PASSWORD와DB_PASSWORD가 모두 설정되어 있습니다. application.yml의 docker 프로파일은${DB_PASSWORD}만 사용하므로 Spring 환경 변수가 불필요할 수 있습니다.중복을 제거하여 설정을 단순화하세요:
environment: SPRING_PROFILES_ACTIVE: docker - SPRING_DATASOURCE_URL: jdbc:mysql://mysql-reservation:3306/wellmeet_reservation - SPRING_DATASOURCE_USERNAME: root - SPRING_DATASOURCE_PASSWORD: password DB_PASSWORD: password EUREKA_CLIENT_SERVICE_URL_DEFAULTZONE: http://discovery-server:8761/eureka/application.yml의 docker 프로파일에서 이미 URL과 username을 정의하고 있으므로,
DB_PASSWORD만 환경 변수로 주입하면 됩니다.domain-reservation/src/main/java/com/wellmeet/domain/reservation/dto/CreateReservationRequest.java (1)
16-17: partySize 필드에 최소값 검증 추가 필요
partySize는@NotNull만 있고 최소값 검증이 없어서 0 이하의 값이 허용될 수 있습니다. 예약 인원은 최소 1명 이상이어야 합니다.다음 diff를 적용하여 검증을 추가하세요:
+ @Min(1) @NotNull Integer partySize,claudedocs/phase4-detailed-plan.md (1)
683-721: 코드 블록에 언어 지정 추가 (선택적)Markdown 코드 블록에 언어 식별자를 추가하면 가독성이 향상됩니다. Lines 683, 696, 709의 코드 블록에 언어를 지정하세요.
예시:
-``` +```text domain-reservation └── Reservation CRUD + 도메인 검증
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (13)
CLAUDE.md(1 hunks)claudedocs/microservices-migration-plan.md(1 hunks)claudedocs/phase4-detailed-plan.md(1 hunks)docker-compose.yml(3 hunks)domain-reservation/Dockerfile(1 hunks)domain-reservation/build.gradle(1 hunks)domain-reservation/src/main/java/com/wellmeet/domain/ReservationServiceApplication.java(1 hunks)domain-reservation/src/main/java/com/wellmeet/domain/reservation/controller/DomainReservationController.java(1 hunks)domain-reservation/src/main/java/com/wellmeet/domain/reservation/dto/CreateReservationRequest.java(1 hunks)domain-reservation/src/main/java/com/wellmeet/domain/reservation/dto/ReservationResponse.java(1 hunks)domain-reservation/src/main/java/com/wellmeet/domain/reservation/dto/UpdateReservationRequest.java(1 hunks)domain-reservation/src/main/java/com/wellmeet/domain/reservation/service/ReservationApplicationService.java(1 hunks)domain-reservation/src/main/resources/application.yml(1 hunks)
🧰 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:
domain-reservation/src/main/java/com/wellmeet/domain/reservation/controller/DomainReservationController.javadomain-reservation/src/main/java/com/wellmeet/domain/reservation/service/ReservationApplicationService.javadomain-reservation/src/main/java/com/wellmeet/domain/ReservationServiceApplication.java
🪛 markdownlint-cli2 (0.18.1)
claudedocs/phase4-detailed-plan.md
14-14: Link fragments should be valid
(MD051, link-fragments)
683-683: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
696-696: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
709-709: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
⏰ 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 (15)
domain-reservation/build.gradle (1)
13-15: 마이크로서비스 전환을 위한 의존성 추가 확인Spring Boot Web, Actuator, Eureka Client 의존성이 적절히 추가되었습니다. 이들은 Phase 4의 도메인 예약 서비스 분리를 위해 필수적입니다.
domain-reservation/Dockerfile (2)
28-29: 보안 모범 사례 준수 확인non-root 사용자로 애플리케이션을 실행하는 것은 좋은 보안 관행입니다.
33-33: 메모리 설정 검토 필요Xms512m, Xmx1024m 설정이 예약 도메인의 실제 부하에 적합한지 확인이 필요합니다.
프로덕션 환경에서 메모리 사용량을 모니터링하고 필요시 조정하세요. 특히 동시 예약 처리량이 높은 경우 힙 크기 증가가 필요할 수 있습니다.
domain-reservation/src/main/java/com/wellmeet/domain/ReservationServiceApplication.java (1)
1-14: Phase 4 단계적 전환을 위한 주석 처리 확인애플리케이션 클래스가 의도적으로 주석 처리되어 있습니다. CLAUDE.md 문서에 따르면 Phase 5에서 활성화 예정입니다.
단계적 전환 전략은 이해되나, 완전히 주석 처리된 파일을 유지하는 것보다 다음을 고려해보세요:
- Phase 4에서는 파일 자체를 제거하고 Phase 5에서 추가
- 또는
@Profile("phase5")어노테이션을 활용하여 조건부 활성화현재 방식으로 진행하려면 주석에 Phase 정보를 명시하는 것이 좋습니다:
// Phase 4: 이 클래스는 의도적으로 비활성화되어 있습니다. // Phase 5에서 주석을 제거하여 독립 서비스로 전환합니다. // 관련 이슈: #58domain-reservation/src/main/resources/application.yml (2)
31-46: Phase별 Eureka 설정 문서화 잘 되어 있음Phase 4/5 전환 시점에 대한 주석이 명확하게 작성되어 Eureka 활성화 시점을 이해하기 쉽습니다.
115-119: Docker 환경에서 Eureka 활성화 설정 확인Docker 프로파일에서 Eureka가 활성화되어 있으나, Phase 5 이후라는 주석이 있습니다. Phase 4에서는 비활성화되어야 하는지 확인이 필요합니다.
현재 Phase와 Eureka 활성화 시점이 일치하는지 확인하세요. Phase 4라면 다음과 같이 수정을 고려하세요:
eureka: client: enabled: false # Phase 4에서는 비활성화, Phase 5에서 true로 변경domain-reservation/src/main/java/com/wellmeet/domain/reservation/dto/UpdateReservationRequest.java (1)
5-11: 부분 업데이트 의도 확인 및 검증 추가 고려모든 필드가 nullable이며 검증 어노테이션이 없습니다. PATCH 방식의 부분 업데이트를 의도한 것인지 확인이 필요합니다.
전체 업데이트(PUT)를 의도했다면 검증 추가를 권장합니다:
public record UpdateReservationRequest( @Min(1) @Max(20) Integer partySize, @Size(max = 500) String specialRequest, @NotNull ReservationStatus status ) { }부분 업데이트(PATCH)가 의도라면 서비스 레이어에서 null이 아닌 필드만 업데이트하는 로직이 있는지 확인하세요.
CLAUDE.md (1)
228-456: BFF 패턴 및 분산 트랜잭션 문서화 잘 작성됨Phase별 전환 전략, 책임 분리, 그리고 Saga 패턴에 대한 상세한 가이드가 잘 작성되어 있습니다. 코드 예시와 비교표가 이해를 돕습니다.
docker-compose.yml (1)
125-130: 헬스체크 재시도 설정 추가 확인기존 서비스들에
retries: 3설정이 추가되어 서비스 안정성이 향상되었습니다.Also applies to: 151-156, 177-182
domain-reservation/src/main/java/com/wellmeet/domain/reservation/dto/ReservationResponse.java (1)
19-31: 검증 완료: 코드 변경 불필요Reservation 엔티티의 null-safety 검증 결과:
- specialRequest: 필드는 null 가능하도록 설계됨 (validateRequest() 메서드에서
specialRequest != null조건으로 명시적 처리)- updatedAt/createdAt: BaseEntity에서
@NotNull과@LastModifiedDate/@CreatedDate로 관리되어 항상 non-null 보장- 기타 필드: 모두
@NotNull또는@NotBlank주석으로 보호됨- partySize: 원시형 int, validatePartySize()에서 양수 검증
ReservationResponse.from() 메서드는 Lombok의
@Getter로 자동 생성된 getter를 호출하며, 모든 필드에 대해 null-safe합니다. 정적 팩토리 메서드 매핑 로직은 명확하고 안전합니다.domain-reservation/src/main/java/com/wellmeet/domain/reservation/controller/DomainReservationController.java (1)
20-76: 잘 구현된 REST 컨트롤러HTTP 메서드와 상태 코드를 적절히 사용하고,
@Valid를 통한 입력 검증이 올바르게 적용되어 있습니다. 서비스 레이어로의 위임도 깔끔합니다.domain-reservation/src/main/java/com/wellmeet/domain/reservation/service/ReservationApplicationService.java (1)
13-93: 트랜잭션 경계 및 서비스 로직 적절함
@Transactional(readOnly = true)를 클래스 레벨에 적용하고 쓰기 작업에만@Transactional을 명시한 구조가 좋습니다.updateReservation메서드의 상태 변경과 필드 업데이트 로직도 올바르게 분리되어 있습니다.claudedocs/microservices-migration-plan.md (1)
534-567: DTO 검증 규칙 문서와 구현 불일치문서의 DTO 검증 규칙(line 539:
@Min(1), line 540:@Size(max=255))이 실제CreateReservationRequest구현과 일치하지 않습니다. 실제 코드에서partySize에@Min검증이 없고,specialRequest에@Size검증이 없습니다.일관성을 위해:
- 코드를 문서에 맞게 수정하거나
- 문서를 실제 구현에 맞게 업데이트하세요
기술 부채를 줄이려면 문서에 명시된 검증 규칙을 코드에 적용하는 것을 권장합니다.
claudedocs/phase4-detailed-plan.md (2)
58-84: API 경로 불일치 (Phase 4 계획)이 문서에도
/api/reservations경로가 명시되어 있지만, 실제 컨트롤러는/domain/reservation을 사용합니다.microservices-migration-plan.md와 함께 일관성 있게 수정이 필요합니다.
270-291: DTO 검증 규칙 일관성문서에 명시된 검증 규칙(
@Min(1)for partySize,@Size(max=255)for specialRequest)이 실제 구현에 누락되어 있습니다.CreateReservationRequest.java에 이러한 검증을 추가하여 문서와 코드를 일치시키세요.
| **파일**: `domain-reservation/src/main/java/com/wellmeet/domain/reservation/api/DomainReservationController.java` | ||
|
|
||
| 3. **Dockerfile 생성**: `domain-reservation/Dockerfile` | ||
| **엔드포인트**: | ||
| - POST `/api/reservations` - 예약 생성 (저장만) | ||
| - GET `/api/reservations/member/{memberId}` - 회원별 예약 조회 | ||
| - GET `/api/reservations/restaurant/{restaurantId}` - 식당별 예약 조회 | ||
| - GET `/api/reservations/{id}` - 예약 단건 조회 | ||
| - PUT `/api/reservations/{id}` - 예약 수정 | ||
| - PATCH `/api/reservations/{id}/cancel` - 예약 취소 | ||
| - PATCH `/api/reservations/{id}/confirm` - 예약 확정 | ||
| - GET `/api/reservations/check-duplicate` - 중복 예약 체크 | ||
|
|
||
| 4. **docker-compose.yml 업데이트**: domain-reservation-service 추가 | ||
| ```java | ||
| @RestController | ||
| @RequestMapping("/api/reservations") | ||
| @RequiredArgsConstructor | ||
| public class DomainReservationController { | ||
|
|
||
| private final DomainReservationService domainReservationService; | ||
|
|
||
| // 예약 생성 (저장만) | ||
| @PostMapping | ||
| public ReservationResponse createReservation(@Valid @RequestBody CreateReservationRequest request) { | ||
| Reservation reservation = domainReservationService.createReservation(request); | ||
| return ReservationResponse.from(reservation); | ||
| } | ||
|
|
||
| // 회원별 조회 | ||
| @GetMapping("/member/{memberId}") | ||
| public List<ReservationResponse> getReservationsByMember(@PathVariable String memberId) { | ||
| return domainReservationService.findAllByMemberId(memberId).stream() | ||
| .map(ReservationResponse::from) | ||
| .toList(); | ||
| } | ||
|
|
||
| // ... 나머지 엔드포인트 | ||
| } | ||
| ``` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
문서와 실제 구현 간 API 경로 불일치
문서에는 /api/reservations 경로가 명시되어 있지만, 실제 DomainReservationController는 /domain/reservation 경로를 사용합니다. API 경로의 일관성을 위해 문서를 업데이트하거나 코드를 수정해야 합니다.
실제 구현:
DomainReservationController.javaline 21:@RequestMapping("/domain/reservation")
문서:
- Line 453, 463-487:
/api/reservations경로 명시
🤖 Prompt for AI Agents
In claudedocs/microservices-migration-plan.md around lines 450–487 the
documented endpoints use /api/reservations but the actual controller
(DomainReservationController.java line 21) is annotated with
@RequestMapping("/domain/reservation"); make them consistent by either updating
the docs to reflect /domain/reservation or changing the controller annotation to
@RequestMapping("/api/reservations") and adjusting any path segments in the
controller methods, integration tests, and OpenAPI/swagger configuration
accordingly; ensure only one canonical base path is used across code, docs, and
tests.
| HEALTHCHECK --interval=30s --timeout=3s --start-period=40s --retries=3 \ | ||
| CMD curl -f http://localhost:8085/actuator/health || exit 1 | ||
|
|
||
| RUN apt-get update && apt-get install -y curl && rm -rf /var/lib/apt/lists/* |
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.
curl 설치 순서 문제로 헬스체크 실패 가능
헬스체크 정의(Line 23-24)가 curl 설치(Line 26)보다 먼저 실행됩니다. 이로 인해 컨테이너 시작 시 헬스체크가 실패할 수 있습니다.
다음과 같이 순서를 변경하세요:
+RUN apt-get update && apt-get install -y curl && rm -rf /var/lib/apt/lists/*
+
HEALTHCHECK --interval=30s --timeout=3s --start-period=40s --retries=3 \
CMD curl -f http://localhost:8085/actuator/health || exit 1
-
-RUN apt-get update && apt-get install -y curl && rm -rf /var/lib/apt/lists/*📝 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.
| HEALTHCHECK --interval=30s --timeout=3s --start-period=40s --retries=3 \ | |
| CMD curl -f http://localhost:8085/actuator/health || exit 1 | |
| RUN apt-get update && apt-get install -y curl && rm -rf /var/lib/apt/lists/* | |
| RUN apt-get update && apt-get install -y curl && rm -rf /var/lib/apt/lists/* | |
| HEALTHCHECK --interval=30s --timeout=3s --start-period=40s --retries=3 \ | |
| CMD curl -f http://localhost:8085/actuator/health || exit 1 |
🤖 Prompt for AI Agents
In domain-reservation/Dockerfile around lines 23-26, the HEALTHCHECK uses curl
before curl is installed which can cause startup healthcheck failures; move the
RUN apt-get update && apt-get install -y curl && rm -rf /var/lib/apt/lists/*
line to appear before the HEALTHCHECK (or install curl in an earlier layer/base
image), then keep the HEALTHCHECK as-is so curl is available when the health
probe runs.
| url: jdbc:mysql://localhost:3306/wellmeet_reservation?serverTimezone=Asia/Seoul&characterEncoding=UTF-8 | ||
| username: root | ||
| password: ${DB_PASSWORD:} |
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
로컬 환경 DB 비밀번호 기본값 검증 필요
로컬 환경의 DB 비밀번호 기본값이 빈 문자열입니다. docker-compose.yml의 MySQL 설정과 일치하는지 확인이 필요합니다.
다음 스크립트로 docker-compose.yml의 MySQL 비밀번호 설정을 확인하세요:
🏁 Script executed:
#!/bin/bash
# docker-compose.yml에서 mysql-reservation의 비밀번호 확인
rg -A2 'mysql-reservation:' docker-compose.yml | rg 'MYSQL_ROOT_PASSWORD'Length of output: 0
더 포괄적인 검증을 위해 다음 스크립트를 실행하겠습니다.
---
🏁 Script executed:
```shell
#!/bin/bash
# 1. docker-compose.yml 파일 위치 찾기
echo "=== Searching for docker-compose.yml ==="
fd 'docker-compose\.yml' -type f
# 2. application.yml 위치 확인
echo -e "\n=== Searching for application.yml ==="
fd 'application\.yml' -type f
Length of output: 421
🏁 Script executed:
#!/bin/bash
# 3. docker-compose.yml 내용 확인 (모든 내용)
echo "=== docker-compose.yml 전체 내용 ==="
find . -name 'docker-compose.yml' -type f -exec cat {} \;
# 4. application.yml에서 DB 설정 확인
echo -e "\n=== application.yml의 DB 설정 ==="
find . -name 'application.yml' -type f -exec grep -A5 'spring:' {} + | grep -A3 'datasource:'Length of output: 7720
🏁 Script executed:
#!/bin/bash
# 5. MySQL 관련 환경 변수 및 비밀번호 설정 모두 확인
echo "=== docker-compose.yml에서 MySQL 관련 설정 모두 ==="
find . -name 'docker-compose.yml' -type f -exec grep -i 'mysql\|password' {} +Length of output: 2424
🏁 Script executed:
#!/bin/bash
# 6. application.yml에서 DB 비밀번호 설정 확인
echo "=== application.yml에서 DB 비밀번호 설정 ==="
find . -name 'application.yml' -type f -exec cat {} +Length of output: 5691
로컬 환경 DB 비밀번호 불일치 - 수정 필요
검증 결과, 설정이 일치하지 않습니다:
docker-compose.yml:MYSQL_ROOT_PASSWORD: password,DB_PASSWORD: passwordapplication.yml(로컬 프로파일):password: ${DB_PASSWORD:}→ 기본값이 빈 문자열
DB_PASSWORD 환경 변수를 설정하지 않고 로컬에서 실행하면 빈 비밀번호로 연결을 시도하여 실패합니다. domain-member, domain-owner 등 다른 서비스들은 password: password로 일관되게 설정되어 있습니다.
수정 방법: application.yml 로컬 프로파일의 7-9번 줄을 다음 중 하나로 변경하세요:
password: ${DB_PASSWORD:password}(다른 서비스와 일관성)- 또는
password: password(환경 변수 제거)
🤖 Prompt for AI Agents
In domain-reservation/src/main/resources/application.yml around lines 7 to 9,
the DB password reference uses password: ${DB_PASSWORD:} which defaults to an
empty string and causes local connection failures; update the property to
provide a default matching the docker-compose value so it is consistent with
other services — either change to password: ${DB_PASSWORD:password} or to
password: password depending on whether you prefer an environment-backed or
hardcoded value.
| datasource: | ||
| url: jdbc:mysql://mysql-reservation:3306/wellmeet_reservation?serverTimezone=Asia/Seoul&characterEncoding=UTF-8 | ||
| username: root | ||
| password: ${DB_PASSWORD:wellmeet123} |
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.
Docker 환경 기본 비밀번호 보안 취약
Docker 프로파일의 기본 DB 비밀번호가 하드코딩되어 있습니다 ("wellmeet123"). 프로덕션 환경에서 이 값이 노출될 위험이 있습니다.
다음 개선 사항을 적용하세요:
- 기본값 제거 또는 더 강력한 값으로 변경:
- password: ${DB_PASSWORD:wellmeet123}
+ password: ${DB_PASSWORD}-
Docker Compose나 K8s Secret을 통해 비밀번호 주입을 필수화하세요.
-
로컬 개발 환경용이라면 주석으로 명시:
# 로컬 개발 전용 - 프로덕션에서는 반드시 DB_PASSWORD 환경변수 설정 필요
password: ${DB_PASSWORD:wellmeet123}🤖 Prompt for AI Agents
In domain-reservation/src/main/resources/application.yml around lines 110 to
113, the DB password is hardcoded as a weak default ("wellmeet123"); remove this
insecure default and require the password from the environment (no fallback
value), update deployment docs to inject DB_PASSWORD via Docker Compose or
Kubernetes Secret, and if needed for local dev add a clear commented note above
the property indicating it is only for local development and that production
must set DB_PASSWORD via secrets.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
♻️ Duplicate comments (3)
domain-reservation/src/main/java/com/wellmeet/domain/reservation/controller/DomainReservationController.java (1)
20-21: 문서와의 경로 불일치 확인 필요기본 경로가
/api/reservation(단수)으로 설정되어 있습니다. 이전 리뷰에서 마이그레이션 문서에는/api/reservations(복수)로 명시되어 있다고 지적되었습니다. REST API 관례상 리소스 컬렉션은 복수형을 사용하는 것이 일반적입니다.문서와의 일관성을 위해 경로를
/api/reservations로 변경하거나, 의도된 설계라면 문서를 업데이트해 주세요.-@RequestMapping("/api/reservation") +@RequestMapping("/api/reservations")domain-reservation/src/main/java/com/wellmeet/domain/reservation/service/ReservationApplicationService.java (2)
24-29: TOCTOU 경쟁 조건으로 인한 중복 예약 체크 불필요
alreadyReserved()체크와save()호출 사이에 경쟁 조건(TOCTOU)이 발생할 수 있습니다. 두 개의 동시 요청이alreadyReserved()체크를 통과한 후save()를 시도하면 중복 예약이 발생할 수 있습니다.ReservationDomainService.save()메소드에서 이미DataIntegrityViolationException을 처리하여 중복 예약 예외를 던지므로, 이 체크는 불필요하며 제거하는 것이 좋습니다.@Transactional public ReservationResponse createReservation(CreateReservationRequest request) { - reservationDomainService.alreadyReserved( - request.memberId(), - request.restaurantId(), - request.availableDateId() - ); - Reservation reservation = new Reservation( request.restaurantId(), request.availableDateId(),
62-81: updateReservation 로직 복잡도 및 설계 개선 필요
updateReservation메소드의 로직이 여전히 복잡하고 개선이 필요합니다:
- 상태 변경과 필드 업데이트 혼재: 상태 변경(lines 66-71)과 필드 업데이트(lines 73-77)가 분리되어 있어 가독성이 떨어집니다.
- 불필요한 availableDateId 처리: Line 73에서 기존 예약의
availableDateId를 가져와서 line 77에서update()에 다시 전달하고 있습니다. 이는availableDateId를 실제로 변경할 수 없음에도 혼란을 야기합니다.- 무조건적인 update() 호출: Line 77에서 필드 변경 여부와 관계없이 항상
update()를 호출하여 불필요한 업데이트가 발생할 수 있습니다.엔티티(
Reservation)에 상태 변경과 필드 업데이트를 위한 명확한 메소드(예:updateDetails(),changeStatusTo())를 구현하고, 서비스 레이어에서는 이를 호출하도록 리팩토링하는 것을 권장합니다.
🧹 Nitpick comments (2)
domain-reservation/src/main/java/com/wellmeet/domain/reservation/dto/UpdateReservationRequest.java (1)
11-11:specialRequest필드에 대한 유효성 검사 추가 검토
specialRequest필드에 유효성 검사가 없어 null 값이나 무제한 길이의 문자열을 허용합니다. 비즈니스 요구사항에 따라 최대 길이 제한이나 다른 제약 조건이 필요한지 검토해 주세요.필요한 경우 다음과 같이 유효성 검사를 추가할 수 있습니다:
+ @Size(max = 500) String specialRequest,domain-reservation/src/main/java/com/wellmeet/domain/reservation/controller/DomainReservationController.java (1)
44-60: 목록 조회 엔드포인트에 페이지네이션 고려레스토랑별, 회원별 예약 목록을 조회하는 엔드포인트에 페이지네이션이 적용되어 있지 않습니다. 예약이 많은 레스토랑이나 활동적인 회원의 경우 응답 크기가 과도하게 커질 수 있어 성능 문제가 발생할 수 있습니다.
Pageable파라미터를 추가하고Page<ReservationResponse>를 반환하도록 변경하는 것을 권장합니다.예시:
@GetMapping("/restaurant/{restaurantId}") public ResponseEntity<Page<ReservationResponse>> getReservationsByRestaurant( @PathVariable String restaurantId, Pageable pageable ) { Page<ReservationResponse> responses = reservationApplicationService .getReservationsByRestaurant(restaurantId, pageable); return ResponseEntity.ok(responses); }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (3)
domain-reservation/src/main/java/com/wellmeet/domain/reservation/controller/DomainReservationController.java(1 hunks)domain-reservation/src/main/java/com/wellmeet/domain/reservation/dto/UpdateReservationRequest.java(1 hunks)domain-reservation/src/main/java/com/wellmeet/domain/reservation/service/ReservationApplicationService.java(1 hunks)
🧰 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:
domain-reservation/src/main/java/com/wellmeet/domain/reservation/controller/DomainReservationController.javadomain-reservation/src/main/java/com/wellmeet/domain/reservation/service/ReservationApplicationService.java
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: build-and-push
🔇 Additional comments (3)
domain-reservation/src/main/java/com/wellmeet/domain/reservation/controller/DomainReservationController.java (3)
24-28: 생성자 주입 방식 잘 적용됨생성자를 통한 의존성 주입이 올바르게 구현되어 있습니다. 이는 Spring의 권장 방식이며 테스트 가능성도 높입니다.
30-36: 생성 엔드포인트 구현 적절함POST 엔드포인트가 적절한 HTTP 상태 코드(201 Created)를 반환하고 있으며,
@Valid를 통한 요청 검증도 올바르게 적용되어 있습니다.
62-75: 업데이트 및 취소 엔드포인트 구현 적절함
- 업데이트 엔드포인트는
@Valid를 통한 검증과 함께 적절히 구현되어 있습니다.- 취소 엔드포인트는
PATCH메서드와 204 No Content 응답을 사용하여 REST 관례를 잘 따르고 있습니다.
🚩 Issue
closed #58
🗣️ 리뷰 요구사항 (선택)
Summary by CodeRabbit
릴리스 노트
새로운 기능
문서
Chores