Skip to content

Conversation

@ehddms0320
Copy link

@ehddms0320 ehddms0320 commented Jun 1, 2025

📌 주요 변경 사항

  • 모집글 상세 조회 시 존재하지 않을 경우 NOT_FOUND 코드로 응답
  • 기존의 IllegalArgumentExceptionCustomException 전환
  • Swagger 요청에 대해 GlobalExceptionHandler 예외 통과 처리
  • RecruitmentDetailDto → 내부 UserDto/StoreDto 구조 변경 및 null 안전 처리
  • 참여자 없는 경우도 예외 없이 처리되도록 controller 수정

✅ 테스트 방법

  • 모집글 상세 조회 성공 /api/v1/recruitments/{id}
  • 존재하지 않는 모집글 조회 시 isSuccess=false & NOT_FOUND
  • 참여자 없는 경우에도 정상 조회
  • Swagger UI 정상 작동 (/swagger-ui/index.html)

🔎 기타 참고

  • ErrorCode.java, CustomException.java, GlobalExceptionHandler.java 변경 없음
  • Swagger 관련 예외를 잡지 않도록 default throw 처리
  • PR 승인 후 develop 브랜치 기준으로 머지 예정

Summary by CodeRabbit

  • 신규 기능

    • 모집글 생성 시 작성자가 자동으로 참여자로 등록됩니다.
    • 모집글 상세 조회 시 참여자 정보와 주문 ID 목록이 함께 제공됩니다.
    • 사용자 정보를 외부 서비스에서 조회하여 상세 정보에 포함합니다.
    • 사용자 조회를 위한 클라이언트 기능이 추가되었습니다.
    • RestTemplate 빈이 추가되어 HTTP 통신이 관리됩니다.
  • 개선 사항

    • 모든 API 응답이 통일된 구조로 제공되어 성공/실패 메시지와 데이터를 일관되게 확인할 수 있습니다.
    • 에러 발생 시 상세한 코드와 메시지로 안내가 강화되었습니다.
    • 모집글 생성 및 수정 시 메뉴 정보의 필수 입력 및 최소 1개 이상 선택이 검증됩니다.
    • 예외 처리 로직이 개선되어 잘못된 요청에 대해 명확한 응답을 제공합니다.
    • 주문 생성 시 응답 검증과 예외 처리가 강화되었습니다.
    • 데이터 전송 객체(DTO) 구조가 개선되어 사용자, 가게 등 주요 정보가 명확하게 구분되어 전달됩니다.
    • 엔티티 필드에 제약 조건과 초기화가 추가되어 데이터 무결성이 향상되었습니다.
  • 버그 수정

    • 잘못된 요청이나 권한 없는 접근 시 명확한 상태 코드와 에러 메시지가 반환됩니다.

@ehddms0320 ehddms0320 deployed to recruitment-service June 1, 2025 06:35 — with GitHub Actions Active
@coderabbitai
Copy link

coderabbitai bot commented Jun 1, 2025

Walkthrough

이 변경사항은 모집(Recruitment) 관련 API의 응답 형식을 ApiResponse로 통일하고, 예외 처리 및 유효성 검증을 강화하는 리팩토링입니다. 컨트롤러, DTO, 엔티티, 서비스 계층에 걸쳐 응답 구조와 에러 처리, 데이터 검증, DTO 구조 개선, 참여자 자동 등록 등이 반영되었습니다.

Changes

파일/경로 요약 변경 내용 요약
RecruitmentController.java 모든 엔드포인트의 반환 타입을 ApiResponse로 통일, 상세 에러 처리, 예외 발생 시 CustomExceptionErrorCode 사용, 참여자 및 주문 정보 포함 상세 DTO 반환, 유효성 검증 추가, 작성자 검증 및 예외 처리 강화
RecruitmentDetailDto.java 엔티티 직접 노출 대신 UserDto, StoreDto 등 중첩 DTO로 구조 변경, 참여자 및 주문 ID 목록 필드 추가, 생성자 추가
RecruitmentResponseDto.java 모집 요약 응답용 신규 DTO 클래스 및 중첩 UserDto, StoreDto 추가
RecruitmentRequestDto.java 메뉴 필드에 @NotNull, @Size(min=1) 등 유효성 검증 어노테이션 추가
Recruitment.java JPA 관계 명확화(지연 로딩, not null), 컬럼 제약 추가, 리스트 초기화, 주석 보강 등
RecruitmentParticipant.java 관계에 지연 로딩 명시, 주석 추가, @PrePersist로 참여 시간 자동 세팅
User.java 테이블명 명시(user), 컬럼 제약 및 유니크 추가, Hibernate 프록시 직렬화 무시
RecruitmentService.java 모집 생성 시 생성자를 자동 참여자로 등록, 반환값을 모집 ID로 변경
OrderClient.java 주문 생성시 응답 구조 및 예외 처리 강화, 상세 오류 메시지 및 검증 추가
GlobalExceptionHandler.java IllegalArgumentException 전용 핸들러 추가, Swagger 경로 예외는 무시, Bad Request 응답 구조 개선
RestTemplateConfig.java RestTemplate 빈 등록을 위한 설정 클래스 추가
UserClient.java 사용자 정보 조회를 위한 REST 클라이언트 서비스 추가
application.properties 사용자 서버 URL 설정 추가 (user.server.url=http://localhost:8080)

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant RecruitmentController
    participant RecruitmentService
    participant OrderClient
    participant RecruitmentParticipantRepo
    participant UserRepo
    participant StoreRepo

    Client->>RecruitmentController: 모집 생성 요청(RecruitmentRequestDto)
    RecruitmentController->>RecruitmentService: createRecruitment(dto)
    RecruitmentService->>UserRepo: 사용자 조회
    RecruitmentService->>StoreRepo: 가게 조회
    RecruitmentService->>RecruitmentParticipantRepo: 모집 저장
    RecruitmentService->>OrderClient: 주문 생성 요청
    OrderClient-->>RecruitmentService: 주문 ID 반환
    RecruitmentService->>RecruitmentParticipantRepo: 생성자 참여자 등록
    RecruitmentService-->>RecruitmentController: 모집 ID 반환
    RecruitmentController-->>Client: ApiResponse<모집 ID> 반환
Loading

Possibly related PRs

  • GCU-Project-Space/Server#41: 이 PR은 ApiResponse 래퍼 도입과 표준화된 예외 처리의 기반을 마련한 PR로, 본 변경사항이 이를 확장 및 완성합니다.

Poem

🐰
API가 반짝, 응답이 통일!
예외도 딱딱, 에러코드로 정리!
참여자 자동 등록, 메뉴도 꼭 확인,
DTO도 새단장, 데이터가 반긴다네.
토끼는 깡총, 코드가 더 깔끔해진 오늘!
🥕✨

✨ Finishing Touches
  • 📝 Generate Docstrings

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
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

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: 4

🔭 Outside diff range comments (1)
recruitment-service/src/main/java/com/example/recruitment/service/RecruitmentService.java (1)

81-89: 🛠️ Refactor suggestion

참여 메소드의 예외 처리도 개선 필요

joinRecruitment 메소드에서도 동일하게 RuntimeException 대신 CustomException을 사용하는 것이 일관성 있는 예외 처리를 위해 바람직합니다.

🧹 Nitpick comments (3)
recruitment-service/src/main/java/com/example/recruitment/dto/RecruitmentResponseDto.java (1)

24-32: DTO 구조 설계 승인

RecruitmentResponseDto의 설계가 잘 구성되어 있습니다:

  • 엔티티에서 DTO로의 명확한 매핑
  • 중첩된 UserDtoStoreDto를 통한 적절한 데이터 캡슐화
  • Lombok을 활용한 보일러플레이트 코드 감소

다만 null 안전성을 위해 개선을 제안합니다.

 public RecruitmentResponseDto(Recruitment recruitment) {
     this.id = recruitment.getId();
     this.title = recruitment.getTitle();
     this.description = recruitment.getDescription();
     this.status = recruitment.getStatus();
     this.deadlineTime = recruitment.getDeadlineTime();
-    this.user = new UserDto(recruitment.getUser());
-    this.store = new StoreDto(recruitment.getStore());
+    this.user = recruitment.getUser() != null ? new UserDto(recruitment.getUser()) : null;
+    this.store = recruitment.getStore() != null ? new StoreDto(recruitment.getStore()) : null;
 }
recruitment-service/src/main/java/com/example/recruitment/service/OrderClient.java (2)

49-49: 로깅 시스템 개선 필요

현재 System.err.println을 사용하고 있는데, 프로덕션 환경에서는 적절한 로깅 프레임워크(예: SLF4J + Logback)를 사용하는 것이 바람직합니다.

다음과 같이 개선하는 것을 권장합니다:

+ import lombok.extern.slf4j.Slf4j;

+ @Slf4j
  @Service
  public class OrderClient {
  
- System.err.println("Order 서버 호출 중 예외 발생: " + e.getMessage());
+ log.error("Order 서버 호출 중 예외 발생", e);

16-16: 하드코딩된 URL 개선 고려

서버 URL이 하드코딩되어 있습니다. 환경별로 다른 URL을 사용해야 할 경우를 대비하여 application.properties나 환경 변수로 관리하는 것을 고려해보세요.

예시:

@Value("${order.service.url:http://54.66.149.225:8100}")
private String serverUrl;
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f478d9b and b2dbf13.

📒 Files selected for processing (10)
  • recruitment-service/src/main/java/com/example/recruitment/controller/RecruitmentController.java (3 hunks)
  • recruitment-service/src/main/java/com/example/recruitment/dto/RecruitmentDetailDto.java (2 hunks)
  • recruitment-service/src/main/java/com/example/recruitment/dto/RecruitmentRequestDto.java (1 hunks)
  • recruitment-service/src/main/java/com/example/recruitment/dto/RecruitmentResponseDto.java (1 hunks)
  • recruitment-service/src/main/java/com/example/recruitment/entity/Recruitment.java (1 hunks)
  • recruitment-service/src/main/java/com/example/recruitment/entity/RecruitmentParticipant.java (2 hunks)
  • recruitment-service/src/main/java/com/example/recruitment/entity/User.java (1 hunks)
  • recruitment-service/src/main/java/com/example/recruitment/exception/GlobalExceptionHandler.java (3 hunks)
  • recruitment-service/src/main/java/com/example/recruitment/service/OrderClient.java (1 hunks)
  • recruitment-service/src/main/java/com/example/recruitment/service/RecruitmentService.java (3 hunks)
🔇 Additional comments (31)
recruitment-service/src/main/java/com/example/recruitment/dto/RecruitmentRequestDto.java (1)

36-37: 유효성 검증 개선 승인

메뉴 필드에 @NotNull@Size(min = 1) 어노테이션을 추가한 것은 좋은 개선사항입니다. 이를 통해 다음과 같은 이점을 얻을 수 있습니다:

  • 메뉴 리스트가 null이 되는 것을 방지
  • 최소 하나 이상의 메뉴 선택을 강제하여 비즈니스 로직의 무결성 보장
  • 클라이언트에게 명확한 검증 메시지 제공
recruitment-service/src/main/java/com/example/recruitment/exception/GlobalExceptionHandler.java (1)

52-55: Swagger 예외 처리 개선 승인

Swagger 관련 요청(/v3/api-docs, /swagger 경로)에 대한 예외를 다시 throw하여 Swagger UI가 정상적으로 작동할 수 있도록 한 것은 훌륭한 개선사항입니다. 이를 통해 API 문서화 기능이 글로벌 예외 핸들러에 의해 방해받지 않게 됩니다.

recruitment-service/src/main/java/com/example/recruitment/entity/RecruitmentParticipant.java (2)

20-20: 지연 로딩 설정 승인

@ManyToOne 관계에 명시적으로 fetch = FetchType.LAZY를 설정한 것은 성능 최적화 측면에서 우수한 개선사항입니다. 이를 통해 필요하지 않은 경우 연관 엔티티를 로드하지 않아 N+1 문제를 방지할 수 있습니다.

Also applies to: 25-25


38-43: @PrePersist 생명주기 콜백 승인

@PrePersist 어노테이션을 사용하여 joinedAt 필드를 자동으로 설정하는 것은 좋은 접근법입니다. 이를 통해:

  • 참여 시간이 자동으로 설정되어 데이터 일관성 보장
  • null 체크를 통해 기존 값이 있는 경우 덮어쓰지 않음
  • 엔티티 저장 시점의 타임스탬프를 정확히 기록
recruitment-service/src/main/java/com/example/recruitment/dto/RecruitmentResponseDto.java (1)

34-46: 중첩 DTO 클래스 설계 승인

UserDtoStoreDto 중첩 클래스들이 잘 설계되었습니다:

  • static 중첩 클래스로 적절한 캡슐화
  • 필요한 필드만 노출하여 데이터 은닉 원칙 준수
  • 일관된 생성자 패턴 사용

이러한 구조는 API 응답의 일관성을 높이고 유지보수성을 향상시킵니다.

Also applies to: 48-62

recruitment-service/src/main/java/com/example/recruitment/entity/Recruitment.java (6)

11-12: Lombok 어노테이션 추가 확인됨

@Getter@Setter 어노테이션이 추가되어 보일러플레이트 코드가 줄어들었습니다. 좋은 개선사항입니다.


22-24: ManyToOne 관계 설정이 개선됨

FetchType.LAZYnullable = false 설정이 추가되어 성능과 데이터 무결성이 향상되었습니다. 지연 로딩은 N+1 문제를 방지하는 데 도움이 됩니다.


27-29: Store 관계 설정도 일관성 있게 개선됨

User와 동일하게 지연 로딩과 null 불가 제약이 적용되어 일관성 있는 설계가 되었습니다.


33-33: 컬렉션 초기화로 NPE 방지

participants 리스트를 빈 ArrayList로 초기화하여 NullPointerException을 방지했습니다. 이는 좋은 방어적 프로그래밍 관행입니다.


36-49: 필수 필드들에 null 불가 제약 추가

모든 필수 필드에 @Column(nullable = false) 어노테이션을 추가하여 데이터베이스 레벨에서 데이터 무결성을 보장하게 되었습니다. 특히 status 필드에 예시 값들이 주석으로 추가된 것도 좋습니다.


57-57: orderIds 컬렉션도 초기화됨

orderIds 리스트도 빈 ArrayList로 초기화하여 일관성을 유지했습니다.

recruitment-service/src/main/java/com/example/recruitment/entity/User.java (4)

8-8: 테이블명 백틱 처리로 예약어 충돌 방지

user는 많은 데이터베이스에서 예약어이므로 백틱으로 감싸는 것이 좋은 방어적 조치입니다.


13-13: Jackson 직렬화 에러 방지

@JsonIgnoreProperties로 Hibernate 프록시 관련 속성들을 무시하도록 설정하여 직렬화 시 발생할 수 있는 에러를 방지했습니다.


17-17: ID 생성 전략 명시적 지정

GenerationType.IDENTITY를 명시적으로 지정하여 의도가 명확해졌습니다.


20-24: 필드 제약 조건 강화

nameemail 필드에 nullable = false 제약을 추가하고, email에는 unique = true까지 추가하여 데이터 무결성이 크게 향상되었습니다.

recruitment-service/src/main/java/com/example/recruitment/service/RecruitmentService.java (4)

36-36: 메소드 반환 타입 개선

createRecruitment 메소드가 이제 생성된 모집글의 ID를 반환하도록 변경되었습니다. 이는 API 응답에서 생성된 리소스의 식별자를 제공할 수 있게 해주는 좋은 개선사항입니다.


51-51: 엔터티 저장 후 ID 생성 확인

recruitmentRepository.save(recruitment) 후 주석으로 "ID 생성됨"을 명시한 것이 좋습니다. 이후 로직에서 recruitment.getId()를 사용하므로 이 시점에서 ID가 생성되는 것이 중요합니다.


63-68: 모집자 자동 참여 등록 로직 추가

모집자를 자동으로 참여자로 등록하는 로직이 추가되었습니다. 이는 비즈니스 로직상 합리적이며, 트랜잭션 내에서 처리되어 데이터 일관성이 보장됩니다.


69-69: 생성된 ID 반환

메소드가 이제 생성된 모집글의 ID를 반환하여 호출자가 생성된 리소스를 식별할 수 있게 되었습니다.

recruitment-service/src/main/java/com/example/recruitment/service/OrderClient.java (1)

22-51: 에러 핸들링 대폭 강화됨

createOrder 메소드의 에러 핸들링이 크게 개선되었습니다:

  • HTTP 응답 상태 코드 검증
  • 응답 본문 구조 검증 (data, orderId 필드 존재 확인)
  • orderIdLong 변환 검증
  • 상세한 에러 메시지 제공

이러한 개선으로 외부 서비스 호출의 안정성이 크게 향상되었습니다.

recruitment-service/src/main/java/com/example/recruitment/dto/RecruitmentDetailDto.java (4)

3-3: 적절한 import 추가

새로운 생성자에서 Recruitment 엔티티를 사용하기 위한 import가 올바르게 추가되었습니다.


22-25: 엔티티에서 DTO로의 적절한 변환

엔티티를 직접 노출하는 대신 중첩 DTO 클래스를 사용하는 것은 좋은 설계입니다. 이는 다음과 같은 이점을 제공합니다:

  • 엔티티와 API 응답의 분리
  • 민감한 정보 노출 방지
  • 응답 구조의 유연성 확보

새로 추가된 orderIds 필드는 참여자의 주문 ID 목록을 제공하여 추가적인 정보를 제공합니다.


27-37: 생성자 구현이 올바르게 작성됨

새로운 생성자가 모든 필드를 적절하게 초기화하고 있습니다:

  • 기본 필드들은 Recruitment 엔티티에서 직접 매핑
  • 중첩 DTO들은 각각의 생성자를 통해 변환
  • 이미 변환된 참여자 목록과 주문 ID 목록을 받아 할당

주석을 통해 participantUsers가 이미 변환된 상태로 전달된다는 것을 명확히 표시한 것도 좋습니다.


39-67: 중첩 DTO 클래스가 적절하게 구현됨

UserDtoStoreDto 중첩 클래스들이 잘 설계되었습니다:

  • 필요한 필드만 선별적으로 노출하여 보안 강화
  • 각 엔티티에서 DTO로 변환하는 생성자 제공
  • static 중첩 클래스로 구현하여 응집도 향상
  • Lombok 어노테이션으로 보일러플레이트 코드 감소

이러한 구조는 API 응답의 일관성과 유지보수성을 크게 향상시킵니다.

recruitment-service/src/main/java/com/example/recruitment/controller/RecruitmentController.java (7)

3-3: 필요한 import문들이 적절하게 추가됨

ApiResponse 표준화, 새로운 DTO 클래스, 예외 처리 개선, 그리고 추가 리포지토리 사용을 위한 import문들이 올바르게 추가되었습니다.

Also applies to: 6-6, 10-12, 15-15, 19-19, 25-25


38-43: API 응답 형식이 올바르게 표준화됨

모집글 생성 엔드포인트가 다음과 같이 개선되었습니다:

  • ApiResponse 래퍼를 사용한 일관된 응답 형식
  • HTTP 201 Created 상태 코드 사용
  • 생성된 리소스의 ID를 반환하여 RESTful 설계 원칙 준수

이러한 변경은 API의 일관성과 사용성을 크게 향상시킵니다.


46-51: 참여 엔드포인트 개선이 적절함

  • @Valid 어노테이션 추가로 요청 데이터 유효성 검증 강화
  • ApiResponse 래퍼를 사용한 일관된 응답 형식 적용
  • 명확한 성공 메시지 제공

이러한 변경으로 API의 안정성과 일관성이 향상되었습니다.


54-60: 조회 엔드포인트의 응답 구조가 적절하게 개선됨

  • RecruitmentResponseDto를 사용하여 엔티티 직접 노출 방지
  • 스트림 API를 활용한 효율적인 변환 로직
  • 표준화된 응답 형식으로 일관성 확보

엔티티 대신 DTO를 사용하는 것은 API 보안과 유지보수성 측면에서 모범 사례입니다.


111-157: 상태 관리 엔드포인트들이 일관되게 개선됨

모든 상태 업데이트 엔드포인트에서 다음과 같은 개선사항이 적용되었습니다:

  • CustomException을 사용한 표준화된 예외 처리
  • ApiResponse.fail을 사용한 에러 응답 표준화
  • 적절한 HTTP 상태 코드 사용 (400 Bad Request, 200 OK)
  • 명확한 에러 메시지와 코드 제공

특히 비즈니스 로직 검증 실패 시 적절한 에러 응답을 반환하는 것이 우수합니다.


167-190: 수정 엔드포인트의 보안과 검증이 강화됨

다음과 같은 중요한 개선사항들이 적용되었습니다:

  • 작성자 권한 검증 유지로 보안 강화
  • Store 엔티티 존재 여부 확인으로 데이터 무결성 보장
  • CustomException을 사용한 일관된 예외 처리
  • 적절한 HTTP 상태 코드 사용 (403 Forbidden)

StoreRepository를 통한 Store 엔티티 유효성 검사 추가는 특히 중요한 개선사항입니다.


92-108: 나머지 엔드포인트들도 일관되게 개선됨

사용자별 조회 엔드포인트와 삭제 엔드포인트에서도 다음과 같은 개선사항이 적용되었습니다:

  • RecruitmentResponseDto를 사용한 안전한 응답 구조
  • ApiResponse 래퍼를 사용한 일관된 응답 형식
  • CustomException을 사용한 표준화된 예외 처리
  • 명확한 성공/실패 메시지 제공

전체 컨트롤러에 걸쳐 일관된 패턴이 적용되어 유지보수성이 크게 향상되었습니다.

Also applies to: 160-165

Comment on lines +39 to +45
// ❗ IllegalArgumentException 처리 추가
@ExceptionHandler(IllegalArgumentException.class)
public ResponseEntity<ApiResponse<Object>> handleIllegalArgument(IllegalArgumentException e) {
return ResponseEntity
.badRequest()
.body(ApiResponse.fail(4000, e.getMessage(), HttpStatus.BAD_REQUEST));
}
Copy link

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

IllegalArgumentException 핸들러 추가 승인

IllegalArgumentException에 대한 전용 핸들러를 추가한 것은 좋은 개선사항입니다. ApiResponse.fail() 메서드를 사용하여 일관된 응답 형식을 유지하고 있습니다.

다만 하드코딩된 에러 코드 4000이 다른 에러 코드들과 일관성을 유지하는지 확인이 필요합니다.


🏁 Script executed:

#!/bin/bash
# Description: Check if error code 4000 is consistent with other error codes in the system

# Search for other error codes in the codebase
rg -A 2 -B 2 "ApiResponse\.fail\(" 

# Search for ErrorCode enum or similar constants
rg -A 5 "class.*ErrorCode|enum.*ErrorCode"

Length of output: 5100


IllegalArgumentException 핸들러 추가 – 에러 코드 관리 일관성 필요

IllegalArgumentException 핸들러 구현은 적절하나, 아래와 같이 하드코딩된 4000 코드 관리를 개선할 것을 권장드립니다.

  • ErrorCode enum에 신규 상수 추가
    • 예: INVALID_ARGUMENT(4000, HttpStatus.BAD_REQUEST, "잘못된 인자입니다.")
    • 파일: recruitment-service/src/main/java/com/example/recruitment/exception/ErrorCode.java
  • GlobalExceptionHandler에서 하드코딩된 4000ErrorCode.INVALID_ARGUMENT.getCode() 등으로 교체
    • 파일: recruitment-service/src/main/java/com/example/recruitment/exception/GlobalExceptionHandler.java

이렇게 수정하면 에러 코드 정의·관리 일관성을 높일 수 있습니다.

🤖 Prompt for AI Agents
In
recruitment-service/src/main/java/com/example/recruitment/exception/GlobalExceptionHandler.java
around lines 39 to 45, replace the hardcoded error code 4000 in the
IllegalArgumentException handler with a constant from the ErrorCode enum. First,
add a new constant INVALID_ARGUMENT with code 4000, HttpStatus.BAD_REQUEST, and
a descriptive message in
recruitment-service/src/main/java/com/example/recruitment/exception/ErrorCode.java.
Then update the handler to use ErrorCode.INVALID_ARGUMENT.getCode() and related
properties instead of hardcoded values to ensure consistent error code
management.

Comment on lines 37 to 40
User user = userRepository.findById(dto.getUserId())
.orElseThrow(() -> new RuntimeException("사용자 없음"));
Store store = storeRepository.findById(dto.getStoreId())
.orElseThrow(() -> new RuntimeException("가게 없음"));
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

예외 처리 개선 필요

현재 RuntimeException을 직접 사용하고 있는데, PR 목표에 따르면 CustomException으로 변경해야 합니다. 또한 에러 메시지가 한국어로 되어 있어 국제화 이슈가 있을 수 있습니다.

다음과 같이 개선하는 것을 권장합니다:

- .orElseThrow(() -> new RuntimeException("사용자 없음"));
+ .orElseThrow(() -> new CustomException(ErrorCode.USER_NOT_FOUND));
- .orElseThrow(() -> new RuntimeException("가게 없음"));
+ .orElseThrow(() -> new CustomException(ErrorCode.STORE_NOT_FOUND));
📝 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
User user = userRepository.findById(dto.getUserId())
.orElseThrow(() -> new RuntimeException("사용자 없음"));
Store store = storeRepository.findById(dto.getStoreId())
.orElseThrow(() -> new RuntimeException("가게 없음"));
User user = userRepository.findById(dto.getUserId())
.orElseThrow(() -> new CustomException(ErrorCode.USER_NOT_FOUND));
Store store = storeRepository.findById(dto.getStoreId())
.orElseThrow(() -> new CustomException(ErrorCode.STORE_NOT_FOUND));
🤖 Prompt for AI Agents
In
recruitment-service/src/main/java/com/example/recruitment/service/RecruitmentService.java
around lines 37 to 40, replace the generic RuntimeException with a
CustomException to align with the PR goals. Also, update the error messages from
Korean to English or use a message code for internationalization support. Ensure
the CustomException includes meaningful error codes or messages that can be
localized.

Comment on lines +30 to +35
}

if (responseBody != null && responseBody.containsKey("data")) {
Map<String, Object> data = (Map<String, Object>) responseBody.get("data");
if (data == null || !data.containsKey("orderId")) {
throw new RuntimeException("Order 서버 응답 'data'에 'orderId'가 없음");
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

예외 처리 일관성 개선

다른 서비스 클래스들과 마찬가지로 RuntimeException 대신 CustomException을 사용하여 예외 처리의 일관성을 유지하는 것이 좋겠습니다.

Also applies to: 45-46, 50-50

🤖 Prompt for AI Agents
In
recruitment-service/src/main/java/com/example/recruitment/service/OrderClient.java
around lines 30-35, 45-46, and 50, replace the use of RuntimeException with
CustomException for throwing errors. This change ensures consistent exception
handling across service classes. Locate all RuntimeException throws in these
lines and update them to throw CustomException instead, preserving the original
error messages.

Comment on lines 72 to 89
public ResponseEntity<ApiResponse<RecruitmentDetailDto>> getRecruitmentDetail(@PathVariable Long recruitmentId) {
Recruitment recruitment = recruitmentRepository.findById(recruitmentId)
.orElseThrow(() -> new CustomException(ErrorCode.NOT_FOUND));

List<RecruitmentParticipant> participants = participantRepository.findByRecruitmentId(recruitmentId);

List<RecruitmentParticipant> participants =
participantRepository.findByRecruitmentId(recruitmentId);
List<User> participantUsers = participants.stream()
.map(RecruitmentParticipant::getUser)
List<RecruitmentDetailDto.UserDto> participantUsers = participants.stream()
.map(p -> new RecruitmentDetailDto.UserDto(p.getUser()))
.toList();

RecruitmentDetailDto dto = new RecruitmentDetailDto();
dto.setId(recruitment.getId());
dto.setTitle(recruitment.getTitle());
dto.setDescription(recruitment.getDescription());
dto.setStatus(recruitment.getStatus());
dto.setDeadlineTime(recruitment.getDeadlineTime());
dto.setUser(recruitment.getUser());
dto.setStore(recruitment.getStore());
dto.setParticipants(participantUsers);

return ResponseEntity.ok(dto);
List<Long> orderIds = participants.stream()
.map(RecruitmentParticipant::getOrderId)
.toList();

RecruitmentDetailDto dto = new RecruitmentDetailDto(recruitment, participantUsers, orderIds);

return ResponseEntity.ok(ApiResponse.ok(dto, "모집 상세 조회 성공"));
}
Copy link

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

상세 조회 엔드포인트가 크게 개선됨

다음과 같은 개선사항들이 적용되었습니다:

  • CustomException을 사용한 표준화된 예외 처리
  • 참여자 정보를 안전하게 DTO로 변환
  • 주문 ID 목록 제공으로 추가 정보 노출
  • 일관된 응답 형식 적용

참여자의 getOrderId() 호출 시 null 안전성을 확인해 보겠습니다:


🏁 Script executed:

#!/bin/bash
# Description: RecruitmentParticipant 엔티티의 orderid 필드 null 안전성 확인

# RecruitmentParticipant 클래스에서 orderId 필드와 getter 메서드 확인
ast-grep --pattern 'class RecruitmentParticipant {
  $$$
  orderId
  $$$
}'

# orderId getter 메서드 확인
rg -A 5 "getOrderId" --type java

Length of output: 944


다음 스크립트로 RecruitmentParticipant 엔티티 파일 경로를 찾고, orderId 필드의 선언부와 null 허용 여부를 함께 확인해 보겠습니다:


🏁 Script executed:

#!/bin/bash
# 1) 엔티티 클래스 파일 위치 확인
entity_file=$(rg -l "class\s\+RecruitmentParticipant" --type java)

echo ">> RecruitmentParticipant 엔티티 파일: $entity_file"

# 2) orderId 필드 선언부 및 nullable 설정 확인
rg -n "@Column.*nullable" -C2 "$entity_file"
rg -n "private\s\+Long\s\+orderId" -C2 "$entity_file"

# 3) getter 구현부 확인
rg -n "getOrderId" -C3 "$entity_file"

Length of output: 376


다음 스크립트로 RecruitmentParticipant 엔티티 파일 위치를 먼저 찾고, orderId 선언부와 nullable 설정, getter 구현을 확인해 보겠습니다:


🏁 Script executed:

#!/bin/bash
# 1) 엔티티 클래스 파일 경로 검색
entity_file=$(fd RecruitmentParticipant.java)

echo ">> RecruitmentParticipant 엔티티 파일 경로: $entity_file"

# 2) 클래스 선언부 확인
rg -n "class RecruitmentParticipant" -C2 "$entity_file"

# 3) orderId 필드 선언부 및 @Column nullable 설정 확인
rg -n "private\s\+Long\s\+orderId" -C2 "$entity_file"
rg -n "@Column.*nullable" -C2 "$entity_file"

# 4) getOrderId() 구현 확인
rg -n "getOrderId" -C3 "$entity_file"

Length of output: 1142


RecruitmentParticipant 엔티티에 orderId 필드 및 getter 미정의로 인한 컴파일 오류

현재 p.getOrderId() 호출 시 대응되는 필드나 메서드가 존재하지 않습니다. 아래 위치를 확인하고 수정이 필요합니다:

  • 파일:
    recruitment-service/src/main/java/com/example/recruitment/entity/RecruitmentParticipant.java

수정 예시:

@Entity
@NoArgsConstructor
@AllArgsConstructor
public class RecruitmentParticipant {
    // ... 기존 필드 ...

    @Column(name = "order_id", nullable = true)
    private Long orderId;

    public Long getOrderId() {
        return this.orderId;
    }

    // ... 나머지 메서드 ...
}

필드와 getter를 추가하거나, Lombok의 @Getter 어노테이션을 클래스 레벨에 적용해 주세요.

🤖 Prompt for AI Agents
In
recruitment-service/src/main/java/com/example/recruitment/entity/RecruitmentParticipant.java
around the declaration of the RecruitmentParticipant class, add a private Long
field named orderId annotated with @Column(name = "order_id", nullable = true)
if it does not exist. Also, add a public getter method getOrderId() that returns
this.orderId. Alternatively, you can add Lombok's @Getter annotation at the
class level to generate the getter automatically. This will fix the compilation
error caused by calling p.getOrderId() in RecruitmentController.java lines
72-89.

@ehddms0320 ehddms0320 had a problem deploying to recruitment-service June 1, 2025 09:25 — with GitHub Actions Failure
@ehddms0320 ehddms0320 closed this Jun 1, 2025
@ehddms0320 ehddms0320 had a problem deploying to recruitment-service June 1, 2025 09:28 — with GitHub Actions Failure
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: 2

♻️ Duplicate comments (1)
recruitment-service/src/main/java/com/example/recruitment/controller/RecruitmentController.java (1)

87-89: ⚠️ Potential issue

RecruitmentParticipant 엔티티에 getOrderId() 메서드 구현 필요

이전 리뷰에서 지적된 대로 RecruitmentParticipant 엔티티에 orderId 필드와 getter 메서드가 정의되어 있지 않아 컴파일 오류가 발생합니다.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b2dbf13 and 8899246.

📒 Files selected for processing (5)
  • recruitment-service/src/main/java/com/example/recruitment/config/RestTemplateConfig.java (1 hunks)
  • recruitment-service/src/main/java/com/example/recruitment/controller/RecruitmentController.java (3 hunks)
  • recruitment-service/src/main/java/com/example/recruitment/dto/RecruitmentDetailDto.java (1 hunks)
  • recruitment-service/src/main/java/com/example/recruitment/service/UserClient.java (1 hunks)
  • recruitment-service/src/main/resources/application.properties (1 hunks)
✅ Files skipped from review due to trivial changes (2)
  • recruitment-service/src/main/resources/application.properties
  • recruitment-service/src/main/java/com/example/recruitment/config/RestTemplateConfig.java
🚧 Files skipped from review as they are similar to previous changes (1)
  • recruitment-service/src/main/java/com/example/recruitment/dto/RecruitmentDetailDto.java
🔇 Additional comments (3)
recruitment-service/src/main/java/com/example/recruitment/controller/RecruitmentController.java (3)

39-45: 모집글 생성 API가 RESTful 원칙에 맞게 개선됨

201 Created 상태 코드와 함께 생성된 리소스 ID를 반환하는 방식이 적절합니다.


141-144: 상태 검증 로직과 에러 응답 형식이 일관되게 개선됨

CONFIRMED 상태 검증과 함께 명확한 에러 코드(4001)와 메시지를 반환합니다.


178-191: 권한 검증 및 연관 엔티티 검증 로직이 적절히 구현됨

작성자 확인과 Store 존재 여부 검증이 추가되어 데이터 무결성이 향상되었습니다.

Comment on lines +20 to +24
public UserDto getUserById(Long userId) {
String url = userServerUrl + "/api/v1/users/" + userId;
ResponseEntity<UserDto> response = restTemplate.getForEntity(url, UserDto.class);
return response.getBody();
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

외부 서비스 호출 시 예외 처리 및 null 안전성 개선 필요

RestTemplate 호출 시 네트워크 오류, 타임아웃, 404 응답 등 다양한 예외가 발생할 수 있으며, response body가 null일 수 있습니다.

아래와 같이 예외 처리와 null 체크를 추가하세요:

-    public UserDto getUserById(Long userId) {
-        String url = userServerUrl + "/api/v1/users/" + userId;
-        ResponseEntity<UserDto> response = restTemplate.getForEntity(url, UserDto.class);
-        return response.getBody();
-    }
+    public UserDto getUserById(Long userId) {
+        try {
+            String url = userServerUrl + "/api/v1/users/" + userId;
+            ResponseEntity<UserDto> response = restTemplate.getForEntity(url, UserDto.class);
+            
+            if (response.getBody() == null) {
+                throw new CustomException(ErrorCode.USER_NOT_FOUND);
+            }
+            
+            return response.getBody();
+        } catch (RestClientException e) {
+            throw new CustomException(ErrorCode.EXTERNAL_SERVICE_ERROR, 
+                "사용자 서비스 호출 실패: " + e.getMessage());
+        }
+    }

또한 URL 생성 시 UriComponentsBuilder 사용을 고려하세요:

String url = UriComponentsBuilder.fromHttpUrl(userServerUrl)
    .path("/api/v1/users/{userId}")
    .buildAndExpand(userId)
    .toUriString();
🤖 Prompt for AI Agents
In
recruitment-service/src/main/java/com/example/recruitment/service/UserClient.java
around lines 20 to 24, the current method lacks exception handling for
RestTemplate calls and does not safely handle null response bodies. Refactor the
method to wrap the RestTemplate call in a try-catch block to handle exceptions
like network errors and timeouts, and check if the response body is null before
returning. Also, replace the URL string concatenation with UriComponentsBuilder
to build the URL safely and cleanly.

Comment on lines +79 to 85
RecruitmentDetailDto.UserDto writer = userClient.getUserById(recruitment.getUser().getId());

List<RecruitmentParticipant> participants =
participantRepository.findByRecruitmentId(recruitmentId);
List<User> participantUsers = participants.stream()
.map(RecruitmentParticipant::getUser)
// ✅ 참여자 정보
List<RecruitmentParticipant> participants = participantRepository.findByRecruitmentId(recruitmentId);
List<RecruitmentDetailDto.UserDto> participantUsers = participants.stream()
.map(p -> userClient.getUserById(p.getUser().getId()))
.toList();
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

UserClient 호출 시 예외 처리 필요

UserClient의 getUserById 메서드는 외부 서비스 호출이므로 실패할 수 있습니다. 특히 반복문 내에서 호출 시 일부 사용자 정보 조회 실패로 전체 요청이 실패할 수 있습니다.

부분 실패를 허용하는 방식으로 개선하거나, 최소한 적절한 예외 처리를 추가하세요:

         List<RecruitmentDetailDto.UserDto> participantUsers = participants.stream()
-                .map(p -> userClient.getUserById(p.getUser().getId()))
+                .map(p -> {
+                    try {
+                        return userClient.getUserById(p.getUser().getId());
+                    } catch (CustomException e) {
+                        // 로그 기록 후 기본값 반환 또는 예외 재발생
+                        return new RecruitmentDetailDto.UserDto(
+                            p.getUser().getId(), 
+                            "사용자 정보 조회 실패", 
+                            null
+                        );
+                    }
+                })
                 .toList();
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
RecruitmentDetailDto.UserDto writer = userClient.getUserById(recruitment.getUser().getId());
List<RecruitmentParticipant> participants =
participantRepository.findByRecruitmentId(recruitmentId);
List<User> participantUsers = participants.stream()
.map(RecruitmentParticipant::getUser)
// ✅ 참여자 정보
List<RecruitmentParticipant> participants = participantRepository.findByRecruitmentId(recruitmentId);
List<RecruitmentDetailDto.UserDto> participantUsers = participants.stream()
.map(p -> userClient.getUserById(p.getUser().getId()))
.toList();
RecruitmentDetailDto.UserDto writer = userClient.getUserById(recruitment.getUser().getId());
// ✅ 참여자 정보
List<RecruitmentParticipant> participants = participantRepository.findByRecruitmentId(recruitmentId);
List<RecruitmentDetailDto.UserDto> participantUsers = participants.stream()
- .map(p -> userClient.getUserById(p.getUser().getId()))
+ .map(p -> {
+ try {
+ return userClient.getUserById(p.getUser().getId());
+ } catch (CustomException e) {
+ // TODO: 로깅 추가
+ return new RecruitmentDetailDto.UserDto(
+ p.getUser().getId(),
+ "사용자 정보 조회 실패",
+ null
+ );
+ }
+ })
.toList();
🤖 Prompt for AI Agents
In
recruitment-service/src/main/java/com/example/recruitment/controller/RecruitmentController.java
around lines 79 to 85, the calls to userClient.getUserById may fail due to
external service issues, especially inside the stream mapping for participants.
To fix this, add exception handling around each getUserById call to catch and
handle failures gracefully, such as returning a default or null user DTO, or
logging the error without failing the entire request. This ensures partial
failures do not cause the whole process to fail.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants