-
Notifications
You must be signed in to change notification settings - Fork 2
[FEAT] member 서버 분리 #64
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
Walkthrough이 PR은 member 서비스를 독립적인 마이크로서비스로 분리하고, REST API 컨트롤러, 애플리케이션 서비스, DTO, 예외 처리를 추가하며, Docker 구성 및 Eureka 클라이언트 설정을 포함합니다. 추가로 restaurant 모듈의 유효성 검사 메시지를 간소화합니다. Changes
Sequence Diagram(s)sequenceDiagram
actor Client
participant MemberController
participant MemberApplicationService
participant MemberDomainService
participant MemberRepository
participant ErrorHandler
Client->>MemberController: POST /api/members (CreateMemberRequest)
MemberController->>MemberApplicationService: createMember(name, nickname, email, phone)
MemberApplicationService->>MemberRepository: save(new Member(...))
MemberRepository-->>MemberApplicationService: Member entity
MemberApplicationService->>MemberApplicationService: MemberResponse.from(Member)
MemberApplicationService-->>MemberController: MemberResponse
MemberController-->>Client: 201 Created + MemberResponse
Client->>MemberController: GET /api/members/{id}
MemberController->>MemberApplicationService: getMemberById(id)
MemberApplicationService->>MemberDomainService: getMemberById(id)
MemberDomainService-->>MemberApplicationService: Member entity
MemberApplicationService-->>MemberController: MemberResponse
MemberController-->>Client: 200 OK + MemberResponse
Client->>MemberController: POST /api/members (invalid request)
rect rgb(255, 200, 200)
MemberController->>ErrorHandler: MethodArgumentNotValidException
ErrorHandler-->>Client: 400 Bad Request + ErrorResponse
end
sequenceDiagram
actor Client
participant FavoriteController
participant FavoriteApplicationService
participant FavoriteRestaurantDomainService
participant Repository
Client->>FavoriteController: POST /api/favorites?memberId=X&restaurantId=Y
FavoriteController->>FavoriteApplicationService: addFavorite(memberId, restaurantId)
rect rgb(200, 220, 255)
Note over FavoriteApplicationService: @Transactional
FavoriteApplicationService->>Repository: save(new FavoriteRestaurant(...))
end
Repository-->>FavoriteApplicationService: saved entity
FavoriteApplicationService-->>FavoriteController: FavoriteRestaurantResponse
FavoriteController-->>Client: 201 Created
Client->>FavoriteController: DELETE /api/favorites?memberId=X&restaurantId=Y
FavoriteController->>FavoriteApplicationService: removeFavorite(memberId, restaurantId)
rect rgb(200, 220, 255)
Note over FavoriteApplicationService: @Transactional
FavoriteApplicationService->>FavoriteRestaurantDomainService: 조회 후 삭제
end
FavoriteApplicationService-->>FavoriteController: void
FavoriteController-->>Client: 204 No Content
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes
Possibly related PRs
Suggested reviewers
Poem
Pre-merge checks and finishing touches❌ Failed checks (2 warnings)
✅ Passed checks (2 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Summary of ChangesHello @unifolio0, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed! 이 PR은 마이크로서비스 전환 로드맵의 중요한 이정표인 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
이 PR은 member 도메인을 별도의 마이크로서비스로 분리하는 작업을 담고 있습니다. 전반적으로 마이그레이션 계획에 따라 컨트롤러, 서비스, DTO, Docker 설정 등 필요한 구성 요소들이 잘 추가되었습니다. 코드 구조와 내용이 명확하며, MSA로의 전환을 위한 중요한 단계로 보입니다. 몇 가지 개선점을 제안드립니다. Docker 이미지 빌드 캐시 최적화, 컨트롤러에서의 일관된 의존성 주입 방식 사용, 예외 처리 시 로깅 추가, 서비스 레이어의 책임 분리 강화 등을 통해 코드의 유지보수성과 안정성을 높일 수 있을 것입니다. 자세한 내용은 각 파일에 대한 리뷰 코멘트를 참고해주세요.
| @ExceptionHandler(Exception.class) | ||
| public ResponseEntity<ErrorResponse> handleException(Exception e) { | ||
| ErrorResponse errorResponse = ErrorResponse.of( | ||
| HttpStatus.INTERNAL_SERVER_ERROR.value(), | ||
| "Internal server error occurred" | ||
| ); | ||
| return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(errorResponse); | ||
| } |
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.
예상치 못한 예외를 처리하는 handleException 메소드에서 예외를 로깅하지 않고 있습니다. 프로덕션 환경에서 원인을 알 수 없는 500 에러가 발생했을 때, 로그가 없으면 원인 파악이 매우 어렵습니다. log.error() 등을 사용하여 예외 스택 트레이스를 기록하는 것이 중요합니다. 클래스에 @Slf4j 어노테이션을 추가하거나 Logger 필드를 직접 선언한 후, 다음과 같이 로그를 추가해주세요.
@ExceptionHandler(Exception.class)
public ResponseEntity<ErrorResponse> handleException(Exception e) {
log.error("Unhandled exception occurred", e);
ErrorResponse errorResponse = ErrorResponse.of(
HttpStatus.INTERNAL_SERVER_ERROR.value(),
"Internal server error occurred"
);
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(errorResponse);
}| SPRING_PROFILES_ACTIVE: local | ||
| SPRING_DATASOURCE_URL: jdbc:mysql://mysql-member:3306/wellmeet_member | ||
| SPRING_DATASOURCE_USERNAME: root | ||
| SPRING_DATASOURCE_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.
| COPY . . | ||
| RUN gradle :domain-member:bootJar --no-daemon |
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 레이어 캐시를 활용하는 것이 좋습니다. 현재 COPY . . 명령어는 소스 코드의 작은 변경만으로도 전체 캐시를 무효화하여 매번 모든 의존성을 새로 다운로드하게 만듭니다.
이를 개선하기 위해, 빌드 파일을 먼저 복사하여 의존성을 다운로드하고, 그 다음에 소스 코드를 복사하는 단계로 나누는 것을 권장합니다. 이렇게 하면 소스 코드만 변경되었을 때 의존성 다운로드 단계를 건너뛰어 빌드 시간을 크게 단축할 수 있습니다.
예시적인 구조는 다음과 같습니다:
# 1. 빌드 관련 파일만 복사
COPY build.gradle settings.gradle ./
# (필요에 따라 각 모듈의 build.gradle도 복사)
# 2. 의존성 다운로드 (이 레이어가 캐시됨)
RUN gradle dependencies
# 3. 전체 소스 코드 복사
COPY . .
# 4. 애플리케이션 빌드
RUN gradle :domain-member:bootJar --no-daemon| public FavoriteRestaurantController(FavoriteRestaurantApplicationService favoriteRestaurantApplicationService) { | ||
| this.favoriteRestaurantApplicationService = favoriteRestaurantApplicationService; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| public MemberController(MemberApplicationService memberApplicationService) { | ||
| this.memberApplicationService = memberApplicationService; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| member.getUpdatedAt() | ||
| ); | ||
| } | ||
| } No newline at end of file |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 7
🧹 Nitpick comments (7)
claudedocs/microservices-migration-plan.md (1)
175-197: 단계 1.2 제목이 실제 상태를 정확히 반영하지 않음제목 "Spring Boot Application 활성화"는 오도적입니다. 실제로는 Application 클래스가 완전히 주석 처리되어 있어 비활성 상태입니다. "빈 스캔 문제"의 근본 원인과 해결 방안을 명시하지 않아, 다른 팀원이 이를 정확히 이해하고 진행하기 어려울 수 있습니다.
제안: 제목을 "Spring Boot Application 클래스 생성 (주석 처리 상태)"로 변경하고, 빈 스캔 문제의 구체적 원인과 해결 예상 시점을 추가하세요.
domain-member/src/main/java/com/wellmeet/domain/member/dto/CreateMemberRequest.java (2)
15-16: 전화번호 형식 검증을 고려해보세요.전화번호 필드에 패턴 검증을 추가하면 데이터 무결성이 향상됩니다.
예시:
public record CreateMemberRequest( // ... @NotBlank + @Pattern(regexp = "^01[0-9]-?[0-9]{3,4}-?[0-9]{4}$", message = "올바른 전화번호 형식이 아닙니다") String phone ) {
5-17: 검증 오류 메시지 커스터마이징을 고려해보세요.더 나은 사용자 경험을 위해 각 검증 어노테이션에 커스텀 메시지를 추가하는 것을 고려해보세요.
예시:
public record CreateMemberRequest( - @NotBlank + @NotBlank(message = "이름은 필수입니다") String name, - @NotBlank + @NotBlank(message = "닉네임은 필수입니다") String nickname,domain-member/src/main/resources/application.yml (1)
14-21: 프로덕션 환경에서 ddl-auto 설정을 확인하세요.
ddl-auto: update는 개발 환경에서는 편리하지만, 프로덕션에서는 의도하지 않은 스키마 변경을 일으킬 수 있습니다. 프로덕션 프로파일에서는validate또는none으로 설정하는 것을 권장합니다.domain-member/src/main/java/com/wellmeet/domain/member/controller/FavoriteRestaurantController.java (2)
44-61: REST API 설계 개선을 고려하세요.POST와 DELETE 엔드포인트가
@RequestParam을 사용하고 있습니다. 더 RESTful한 설계를 위해 다음을 고려해보세요:
- POST: Request body 사용 (e.g.,
{"memberId": "...", "restaurantId": "..."})- DELETE: Path variable 사용 (e.g.,
DELETE /api/favorites/{memberId}/{restaurantId})현재 구현도 동작하지만, REST 관례를 따르면 API 일관성이 향상됩니다.
26-33: 요청 파라미터에 유효성 검증 추가를 고려하세요.
memberId와restaurantId파라미터에 대한 유효성 검증이 없습니다. 빈 문자열이나 null 값 방지를 위해@NotBlank애노테이션 추가를 고려해보세요.예시:
@RequestParam @NotBlank String memberId, @RequestParam @NotBlank String restaurantIdAlso applies to: 44-52, 54-61
domain-member/src/main/java/com/wellmeet/domain/member/controller/MemberController.java (1)
46-52: 배치 조회 엔드포인트의 HTTP 메서드를 재검토하세요.
POST /api/members/batch엔드포인트는 조회 작업에 POST를 사용하고 있어 HTTP 메서드의 의미론과 맞지 않습니다. 다음 방안을 고려하세요:
- GET with query parameter: ID 목록이 적은 경우
GET /api/members?ids=id1,id2,id3- POST to search endpoint: 의도를 명확히 하기 위해
POST /api/members/search또는/api/members/query로 변경현재 구조가 대량의 ID를 처리하기 위한 의도적인 선택이라면, 엔드포인트 이름을 더 명확하게 변경하는 것을 권장합니다.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (21)
claudedocs/microservices-migration-plan.md(3 hunks)docker-compose.yml(1 hunks)domain-member/Dockerfile(1 hunks)domain-member/build.gradle(1 hunks)domain-member/src/main/java/com/wellmeet/domain/MemberServiceApplication.java(1 hunks)domain-member/src/main/java/com/wellmeet/domain/member/MemberDomainService.java(0 hunks)domain-member/src/main/java/com/wellmeet/domain/member/controller/FavoriteRestaurantController.java(1 hunks)domain-member/src/main/java/com/wellmeet/domain/member/controller/MemberController.java(1 hunks)domain-member/src/main/java/com/wellmeet/domain/member/dto/CreateMemberRequest.java(1 hunks)domain-member/src/main/java/com/wellmeet/domain/member/dto/ErrorResponse.java(1 hunks)domain-member/src/main/java/com/wellmeet/domain/member/dto/FavoriteRestaurantResponse.java(1 hunks)domain-member/src/main/java/com/wellmeet/domain/member/dto/MemberIdsRequest.java(1 hunks)domain-member/src/main/java/com/wellmeet/domain/member/dto/MemberResponse.java(1 hunks)domain-member/src/main/java/com/wellmeet/domain/member/exception/MemberExceptionHandler.java(1 hunks)domain-member/src/main/java/com/wellmeet/domain/member/service/FavoriteRestaurantApplicationService.java(1 hunks)domain-member/src/main/java/com/wellmeet/domain/member/service/MemberApplicationService.java(1 hunks)domain-member/src/main/resources/application.yml(1 hunks)domain-restaurant/src/main/java/com/wellmeet/domain/restaurant/dto/AvailableDateIdsRequest.java(1 hunks)domain-restaurant/src/main/java/com/wellmeet/domain/restaurant/dto/DecreaseCapacityRequest.java(1 hunks)domain-restaurant/src/main/java/com/wellmeet/domain/restaurant/dto/IncreaseCapacityRequest.java(1 hunks)domain-restaurant/src/main/java/com/wellmeet/domain/restaurant/dto/RestaurantIdsRequest.java(1 hunks)
💤 Files with no reviewable changes (1)
- domain-member/src/main/java/com/wellmeet/domain/member/MemberDomainService.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:
domain-member/src/main/java/com/wellmeet/domain/member/controller/FavoriteRestaurantController.javadomain-restaurant/src/main/java/com/wellmeet/domain/restaurant/dto/RestaurantIdsRequest.javadomain-member/src/main/java/com/wellmeet/domain/member/dto/FavoriteRestaurantResponse.javaclaudedocs/microservices-migration-plan.mddomain-member/src/main/java/com/wellmeet/domain/member/service/FavoriteRestaurantApplicationService.java
🧬 Code graph analysis (4)
domain-member/src/main/java/com/wellmeet/domain/member/controller/FavoriteRestaurantController.java (1)
domain-member/src/main/java/com/wellmeet/domain/member/controller/MemberController.java (1)
RestController(19-59)
domain-member/src/main/java/com/wellmeet/domain/member/service/MemberApplicationService.java (1)
domain-member/src/main/java/com/wellmeet/domain/member/MemberDomainService.java (1)
Service(11-25)
domain-member/src/main/java/com/wellmeet/domain/member/service/FavoriteRestaurantApplicationService.java (3)
domain-member/src/main/java/com/wellmeet/domain/member/service/MemberApplicationService.java (1)
Service(12-44)domain-member/src/main/java/com/wellmeet/domain/member/MemberDomainService.java (1)
Service(11-25)domain-member/src/main/java/com/wellmeet/domain/member/FavoriteRestaurantDomainService.java (1)
Service(11-37)
domain-member/src/main/java/com/wellmeet/domain/member/controller/MemberController.java (1)
domain-member/src/main/java/com/wellmeet/domain/member/controller/FavoriteRestaurantController.java (1)
RestController(16-62)
⏰ 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 (16)
claudedocs/microservices-migration-plan.md (1)
296-374: Phase 2 상태 표기와 실제 상태의 불일치 - 명확성 필요Phase 2를 "✅ (완료)"로 표시하고 모든 완료 기준을 [x]로 체크했지만, 동시에 "bootJar 빌드 불가", "Docker 컨테이너 실행 검증 보류"라는 중대한 이슈를 명시하고 있습니다. 이는 다음과 같은 혼동을 야기합니다:
- 아키텍처 및 코드 구조는 완료: ✅ (명확함)
- 실제 배포 가능성: ❓ (불명확함)
다른 개발자들이 이 문서를 참고할 때, Phase 2가 프로덕션 배포 가능한 상태인지 확실하게 판단할 수 없습니다.
제안:
- 상태를 "Phase 2: domain-member 구현 ✅ (코드 구조 완료, 배포 검증 보류)" 형태로 명확히 하거나
- "완료"의 의미를 명시하는 섹션을 추가하여, 팀의 공동 이해를 확보하세요
domain-restaurant/src/main/java/com/wellmeet/domain/restaurant/dto/AvailableDateIdsRequest.java (1)
7-8: 검증 메시지 단순화가 적절합니다.커스텀 검증 메시지를 제거하고 기본 메시지를 사용하는 것은 합리적인 선택입니다. 검증 동작은 동일하게 유지됩니다.
domain-restaurant/src/main/java/com/wellmeet/domain/restaurant/dto/RestaurantIdsRequest.java (1)
7-8: 검증 메시지 단순화가 적절합니다.커스텀 검증 메시지를 기본 메시지로 변경하는 것은 일관된 접근 방식이며, 검증 로직에는 영향을 미치지 않습니다.
domain-member/src/main/java/com/wellmeet/domain/member/dto/FavoriteRestaurantResponse.java (1)
6-22: DTO 구현이 깔끔합니다.레코드 기반 DTO와 정적 팩토리 메서드 패턴이 적절하게 구현되었습니다.
domain-member/build.gradle (1)
10-12: 마이크로서비스에 적합한 의존성이 추가되었습니다.REST API 컨트롤러, 헬스 체크, 서비스 디스커버리를 위한 의존성이 올바르게 추가되었습니다.
domain-member/src/main/java/com/wellmeet/domain/member/dto/ErrorResponse.java (1)
5-13: 표준적인 에러 응답 구조입니다.statusCode, message, timestamp를 포함한 에러 응답 DTO가 적절하게 구현되었습니다.
domain-member/src/main/java/com/wellmeet/domain/member/dto/MemberIdsRequest.java (1)
6-9: 배치 요청 DTO가 적절하게 구현되었습니다.@notempty 검증으로 빈 리스트를 방지하고 있으며, 배치 조회 기능에 적합한 구조입니다.
docker-compose.yml (1)
131-155: 멤버 서비스 Docker 구성이 추가되었습니다.서비스 구성이 전반적으로 적절하나, Line 151의 healthcheck가 curl을 사용합니다. 이는 Dockerfile의 slim 이미지에 curl이 포함되지 않아 실패할 수 있습니다. Dockerfile 리뷰 코멘트를 참조하세요.
domain-member/Dockerfile (1)
1-17: 멀티 스테이지 빌드 구조가 적절합니다.Gradle 빌드와 런타임을 분리하여 최종 이미지 크기를 최적화하고 있으며, JVM 메모리 설정과 G1GC 사용도 적절합니다.
domain-member/src/main/java/com/wellmeet/domain/MemberServiceApplication.java (1)
1-12: 이 리뷰 의견은 부정확합니다. 수정이 불필요합니다.
domain-member는 독립 실행 가능한 서비스가 아니라 공유 도메인 라이브러리 모듈입니다. 검증 결과:
build.gradle에서java-test-fixtures플러그인 사용 (라이브러리 모듈 표시)api-user,api-owner,batch-reminder등 여러 서비스에서 의존성으로 사용- 독립 실행 가능한 메인 애플리케이션:
api-user,api-owner,discovery-server,batch-reminder(모두 활성화된@SpringBootApplication포함)- 도메인 모듈(
domain-member,domain-restaurant)의 주석 처리된 진입점은 의도된 설계이는 멀티모듈 마이크로서비스 아키텍처의 정상적인 패턴입니다.
Likely an incorrect or invalid review comment.
domain-member/src/main/java/com/wellmeet/domain/member/dto/MemberResponse.java (1)
6-33: LGTM!DTO 레코드 구조가 깔끔하고, 정적 팩토리 메서드가 Member 엔티티로부터 적절하게 매핑하고 있습니다. 불변성을 보장하는 record 타입 사용이 적절합니다.
domain-member/src/main/resources/application.yml (1)
23-33: LGTM!Eureka 클라이언트 설정이 적절합니다. 랜덤 인스턴스 ID 사용으로 다중 인스턴스 배포를 지원하고 있습니다.
domain-member/src/main/java/com/wellmeet/domain/member/service/MemberApplicationService.java (3)
20-25: 회원 생성 시 중복 검증을 확인하세요.이메일이나 전화번호의 중복 검증이 없습니다. 이러한 필드들이 고유해야 한다면, 생성 전 중복 검증을 추가하거나 데이터베이스 제약조건(unique constraint)이 설정되어 있는지 확인하세요. 제약조건 없이 중복 데이터가 입력되면 데이터 무결성 문제가 발생할 수 있습니다.
중복 검증이 필요한 경우 다음과 같이 추가할 수 있습니다:
@Transactional public MemberResponse createMember(String name, String nickname, String email, String phone) { // 중복 검증 로직 추가 if (memberRepository.existsByEmail(email)) { throw new MemberException(MemberErrorCode.DUPLICATE_EMAIL); } Member member = new Member(name, nickname, email, phone); Member savedMember = memberRepository.save(member); return MemberResponse.from(savedMember); }
39-43: LGTM!삭제 전 엔티티를 조회하여 존재 여부를 검증하는 방식이 안전합니다. 존재하지 않는 회원 삭제 시 명확한 예외가 발생합니다.
32-37: LGTM!배치 조회 메서드가 스트림 API를 효과적으로 사용하고 있습니다. 메서드 참조로 깔끔하게 DTO 변환을 수행합니다.
domain-member/src/main/java/com/wellmeet/domain/member/service/FavoriteRestaurantApplicationService.java (1)
18-27: LGTM!조회 및 삭제 메서드들이 깔끔하게 구현되어 있습니다. 도메인 서비스와 적절히 분리되어 있고, DTO 변환도 명확합니다.
Also applies to: 37-41
| HEALTHCHECK --interval=30s --timeout=3s --start-period=40s \ | ||
| CMD curl -f http://localhost:8082/actuator/health || exit 1 |
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.
HEALTHCHECK가 실패할 수 있습니다.
openjdk:21-jdk-slim 이미지에는 curl이 기본적으로 포함되어 있지 않습니다. 헬스체크가 실패하여 컨테이너가 unhealthy 상태가 됩니다.
해결 방법 1 (권장): 런타임 이미지에 curl 설치
# Stage 2: Runtime
FROM openjdk:21-jdk-slim
WORKDIR /app
+RUN apt-get update && apt-get install -y curl && rm -rf /var/lib/apt/lists/*
COPY --from=build /app/domain-member/build/libs/*.jar app.jar해결 방법 2: wget 사용 (slim 이미지에 포함됨)
HEALTHCHECK --interval=30s --timeout=3s --start-period=40s \
- CMD curl -f http://localhost:8082/actuator/health || exit 1
+ CMD wget --no-verbose --tries=1 --spider http://localhost:8082/actuator/health || exit 1📝 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 \ | |
| CMD curl -f http://localhost:8082/actuator/health || exit 1 | |
| HEALTHCHECK --interval=30s --timeout=3s --start-period=40s \ | |
| CMD wget --no-verbose --tries=1 --spider http://localhost:8082/actuator/health || exit 1 |
🤖 Prompt for AI Agents
In domain-member/Dockerfile around lines 14 to 15, the HEALTHCHECK uses curl but
the openjdk:21-jdk-slim base image doesn't include curl, causing health checks
to fail; fix by either installing curl in the image (add package install in the
image build stage before HEALTHCHECK, e.g., apt-get update && apt-get install -y
curl && cleanup) or change the HEALTHCHECK to use wget (which is present in
slim) or a shell-built-in check (e.g., using /bin/sh to test the port) so the
command succeeds without relying on missing binaries.
| @NotBlank | ||
| String email, |
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.
email 필드에 형식 검증이 필요합니다.
email 필드가 @notblank만으로 검증되고 있습니다. 이메일 형식의 유효성을 보장하기 위해 @Email 어노테이션을 추가해야 합니다.
다음 diff를 적용하세요:
+import jakarta.validation.constraints.Email;
+
public record CreateMemberRequest(
@NotBlank
String name,
@NotBlank
String nickname,
@NotBlank
+ @Email
String email,🤖 Prompt for AI Agents
In
domain-member/src/main/java/com/wellmeet/domain/member/dto/CreateMemberRequest.java
around lines 12 to 13, the email field currently has only @NotBlank; add the
@Email validation annotation to ensure proper email format and add the
corresponding import (javax.validation.constraints.Email) if it is not already
present; keep @NotBlank alongside @Email so both non-empty and format checks are
enforced.
| @ExceptionHandler(MemberException.class) | ||
| public ResponseEntity<ErrorResponse> handleMemberException(MemberException e) { | ||
| ErrorResponse errorResponse = ErrorResponse.of(e.getStatusCode(), e.getMessage()); | ||
| return ResponseEntity.status(e.getStatusCode()).body(errorResponse); | ||
| } | ||
|
|
||
| @ExceptionHandler(MethodArgumentNotValidException.class) | ||
| public ResponseEntity<ErrorResponse> handleValidationException(MethodArgumentNotValidException e) { | ||
| FieldError fieldError = e.getBindingResult().getFieldError(); | ||
| String message = fieldError != null ? fieldError.getDefaultMessage() : "Validation failed"; | ||
|
|
||
| ErrorResponse errorResponse = ErrorResponse.of(HttpStatus.BAD_REQUEST.value(), message); | ||
| return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(errorResponse); | ||
| } | ||
|
|
||
| @ExceptionHandler(IllegalArgumentException.class) | ||
| public ResponseEntity<ErrorResponse> handleIllegalArgumentException(IllegalArgumentException e) { | ||
| ErrorResponse errorResponse = ErrorResponse.of(HttpStatus.BAD_REQUEST.value(), e.getMessage()); | ||
| return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(errorResponse); | ||
| } | ||
|
|
||
| @ExceptionHandler(Exception.class) | ||
| public ResponseEntity<ErrorResponse> handleException(Exception e) { | ||
| ErrorResponse errorResponse = ErrorResponse.of( | ||
| HttpStatus.INTERNAL_SERVER_ERROR.value(), | ||
| "Internal server error occurred" | ||
| ); | ||
| return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(errorResponse); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion | 🟠 Major
예외 핸들러에 로깅을 추가하세요.
예외 핸들러에 로깅이 없어 디버깅과 모니터링이 어렵습니다. 특히 일반 Exception 핸들러(lines 35-42)는 중요한 에러 정보를 숨길 수 있으므로, 최소한 에러 레벨 로그를 추가해야 합니다.
다음과 같이 로깅을 추가하는 것을 권장합니다:
+import lombok.extern.slf4j.Slf4j;
+
+@Slf4j
@RestControllerAdvice
public class MemberExceptionHandler {
@ExceptionHandler(MemberException.class)
public ResponseEntity<ErrorResponse> handleMemberException(MemberException e) {
+ log.warn("MemberException occurred: {}", e.getMessage());
ErrorResponse errorResponse = ErrorResponse.of(e.getStatusCode(), e.getMessage());
return ResponseEntity.status(e.getStatusCode()).body(errorResponse);
}
@ExceptionHandler(Exception.class)
public ResponseEntity<ErrorResponse> handleException(Exception e) {
+ log.error("Unexpected error occurred", e);
ErrorResponse errorResponse = ErrorResponse.of(
HttpStatus.INTERNAL_SERVER_ERROR.value(),
"Internal server error occurred"
);
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(errorResponse);
}Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In
domain-member/src/main/java/com/wellmeet/domain/member/exception/MemberExceptionHandler.java
around lines 14 to 42, the exception handlers currently do not log errors which
hinders debugging and monitoring; add logging to each handler (preferably using
a class-level SLF4J logger or Lombok @Slf4j) and log at error level including
contextual information and the Throwable itself: for MemberException log the
status and message with the exception, for MethodArgumentNotValidException log
the field name and validation message (or binding result) and the exception, for
IllegalArgumentException log the provided message and exception, and for the
generic Exception handler log a clear contextual message and pass the exception
to capture the stack trace; ensure necessary imports or Lombok annotation are
added and keep response behavior unchanged.
| @Transactional | ||
| public FavoriteRestaurantResponse addFavorite(String memberId, String restaurantId) { | ||
| FavoriteRestaurant favoriteRestaurant = new FavoriteRestaurant(memberId, restaurantId); | ||
| favoriteRestaurantDomainService.save(favoriteRestaurant); | ||
| return FavoriteRestaurantResponse.from(favoriteRestaurant); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion | 🟠 Major
즐겨찾기 추가 시 중복 방지 로직을 추가하세요.
동일한 회원-레스토랑 조합에 대한 중복 즐겨찾기 생성을 방지하는 로직이 없습니다. 중복 생성을 허용하지 않으려면 추가 전 존재 여부를 확인해야 합니다.
다음과 같이 중복 검증을 추가하세요:
@Transactional
public FavoriteRestaurantResponse addFavorite(String memberId, String restaurantId) {
+ if (favoriteRestaurantDomainService.isFavorite(memberId, restaurantId)) {
+ throw new MemberException(MemberErrorCode.FAVORITE_ALREADY_EXISTS);
+ }
FavoriteRestaurant favoriteRestaurant = new FavoriteRestaurant(memberId, restaurantId);
favoriteRestaurantDomainService.save(favoriteRestaurant);
return FavoriteRestaurantResponse.from(favoriteRestaurant);
}🤖 Prompt for AI Agents
In
domain-member/src/main/java/com/wellmeet/domain/member/service/FavoriteRestaurantApplicationService.java
around lines 29 to 34, add a pre-save duplicate check for the same memberId and
restaurantId and prevent creating a duplicate favorite: call the
domainService/repository method (e.g., existsByMemberIdAndRestaurantId or
findByMemberIdAndRestaurantId) before creating the FavoriteRestaurant, and if it
exists either return the existing FavoriteRestaurantResponse or throw a
DomainDuplicateException; additionally ensure a unique constraint at the DB
level for (member_id, restaurant_id) to guard against race conditions and handle
DataIntegrityViolationException (map it to the same
duplicate-response/exception) so duplicates are never persisted.
| datasource: | ||
| driver-class-name: com.mysql.cj.jdbc.Driver | ||
| url: jdbc:mysql://localhost:3307/wellmeet_member | ||
| username: root | ||
| 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.
데이터베이스 자격증명을 환경변수로 외부화하세요.
데이터베이스 URL, 사용자명, 비밀번호가 하드코딩되어 있습니다. 환경변수를 사용하여 외부화해야 합니다.
다음 diff를 적용하세요:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
- url: jdbc:mysql://localhost:3307/wellmeet_member
- username: root
- password: password
+ url: ${DB_URL:jdbc:mysql://localhost:3307/wellmeet_member}
+ username: ${DB_USERNAME:root}
+ password: ${DB_PASSWORD}📝 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.
| datasource: | |
| driver-class-name: com.mysql.cj.jdbc.Driver | |
| url: jdbc:mysql://localhost:3307/wellmeet_member | |
| username: root | |
| password: password | |
| datasource: | |
| driver-class-name: com.mysql.cj.jdbc.Driver | |
| url: ${DB_URL:jdbc:mysql://localhost:3307/wellmeet_member} | |
| username: ${DB_USERNAME:root} | |
| password: ${DB_PASSWORD} |
🤖 Prompt for AI Agents
In domain-member/src/main/resources/application.yml around lines 8 to 12, the DB
URL/username/password are hardcoded; change them to reference environment
variables (e.g. use property placeholders like ${DB_URL} / ${DB_USERNAME} /
${DB_PASSWORD} with sensible defaults or none) so credentials are externalized,
update any docs/deployment manifests to set those env vars, and remove hardcoded
secrets from the file before committing.
| @NotNull | ||
| Long availableDateId, | ||
|
|
||
| @NotNull(message = "Party size는 필수입니다") | ||
| @Min(value = 1, message = "Party size는 최소 1명 이상이어야 합니다") | ||
| @NotNull | ||
| Integer partySize |
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.
partySize에 대한 최솟값 검증이 제거되었습니다.
@Min(value = 1) 제약 조건을 제거하면 partySize가 0 또는 음수 값을 허용하게 되어 비즈니스 로직 오류가 발생할 수 있습니다. 파티 크기는 최소 1명 이상이어야 합니다.
다음 diff를 적용하여 최솟값 검증을 복원하세요:
package com.wellmeet.domain.restaurant.dto;
import jakarta.validation.constraints.NotNull;
+import jakarta.validation.constraints.Min;
public record DecreaseCapacityRequest(
@NotNull
Long availableDateId,
@NotNull
+ @Min(1)
Integer partySize
) {
}📝 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.
| @NotNull | |
| Long availableDateId, | |
| @NotNull(message = "Party size는 필수입니다") | |
| @Min(value = 1, message = "Party size는 최소 1명 이상이어야 합니다") | |
| @NotNull | |
| Integer partySize | |
| package com.wellmeet.domain.restaurant.dto; | |
| import jakarta.validation.constraints.NotNull; | |
| import jakarta.validation.constraints.Min; | |
| public record DecreaseCapacityRequest( | |
| @NotNull | |
| Long availableDateId, | |
| @NotNull | |
| @Min(1) | |
| Integer partySize | |
| ) { | |
| } |
🤖 Prompt for AI Agents
In
domain-restaurant/src/main/java/com/wellmeet/domain/restaurant/dto/DecreaseCapacityRequest.java
around lines 6 to 10, the @Min(1) validation on the partySize field was removed
which allows 0 or negative party sizes; restore the minimum-value constraint by
adding @Min(1) above the partySize field (and add the import for
javax.validation.constraints.Min if it's not present) so that partySize must be
at least 1.
| @NotNull | ||
| Long availableDateId, | ||
|
|
||
| @NotNull(message = "Party size는 필수입니다") | ||
| @Min(value = 1, message = "Party size는 최소 1명 이상이어야 합니다") | ||
| @NotNull | ||
| Integer partySize |
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.
partySize에 대한 최솟값 검증이 제거되었습니다.
@Min(value = 1) 제약 조건을 제거하면 partySize가 0 또는 음수 값을 허용하게 됩니다. 이는 비즈니스 로직상 유효하지 않은 값입니다.
다음 diff를 적용하여 최솟값 검증을 복원하세요:
package com.wellmeet.domain.restaurant.dto;
import jakarta.validation.constraints.NotNull;
+import jakarta.validation.constraints.Min;
public record IncreaseCapacityRequest(
@NotNull
Long availableDateId,
@NotNull
+ @Min(1)
Integer partySize
) {
}📝 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.
| @NotNull | |
| Long availableDateId, | |
| @NotNull(message = "Party size는 필수입니다") | |
| @Min(value = 1, message = "Party size는 최소 1명 이상이어야 합니다") | |
| @NotNull | |
| Integer partySize | |
| package com.wellmeet.domain.restaurant.dto; | |
| import jakarta.validation.constraints.NotNull; | |
| import jakarta.validation.constraints.Min; | |
| public record IncreaseCapacityRequest( | |
| @NotNull | |
| Long availableDateId, | |
| @NotNull | |
| @Min(1) | |
| Integer partySize | |
| ) { | |
| } |
🤖 Prompt for AI Agents
In
domain-restaurant/src/main/java/com/wellmeet/domain/restaurant/dto/IncreaseCapacityRequest.java
around lines 6 to 10, the @Min(value = 1) constraint for partySize was removed
causing zero/negative party sizes to be allowed; re-add @Min(1) on the partySize
field (preserving @NotNull), and ensure the javax.validation.constraints.Min
import is present at the top of the file so validation enforces a minimum value
of 1.
🚩 Issue
closed #57
🗣️ 리뷰 요구사항 (선택)
Summary by CodeRabbit
릴리스 노트
New Features
Documentation