[feature] 지원자의 지원서를 요약해서 메모에 추가한다.#809
Conversation
RabbitMQ를 사용하기위한 의존성 추가
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Warning
|
| Cohort / File(s) | Summary |
|---|---|
의존성 변경 backend/build.gradle |
implementation 'org.springframework.boot:spring-boot-starter-amqp' 추가 |
RabbitMQ / 메시지 구성 backend/src/main/java/moadong/global/config/RabbitMQConfig.java, backend/src/main/java/moadong/club/payload/dto/ApplicantSummaryMessage.java |
RabbitMQ 연결/큐/익스체인지/바인딩/DLX 및 ApplicantSummaryMessage 레코드 추가 |
퍼블리셔 backend/src/main/java/moadong/club/summary/ApplicantIdMessagePublisher.java |
RabbitTemplate로 ApplicantSummaryMessage 발행하는 컴포넌트 추가 |
컨슈머 및 요약 처리 backend/src/main/java/moadong/club/summary/ApplicantIdMessageConsumer.java |
RabbitMQ 리스너 추가: DB 조회, AESCipher 복호화, GemmaService 호출로 요약 생성 후 applicant.memo 업데이트 및 저장, 오류 처리 포함 |
서비스 통합 backend/src/main/java/moadong/club/service/ClubApplyPublicService.java |
ApplicantIdMessagePublisher 주입 및 지원자 저장 후 메시지 발행 로직 추가, @Transactional 적용 |
Gemma 통신 계층 backend/src/main/java/moadong/gemma/service/GemmaService.java, backend/src/main/java/moadong/gemma/dto/AIRequest.java, backend/src/main/java/moadong/gemma/dto/AIResponse.java |
RestTemplate/ObjectMapper 기반 Gemma 호출 서비스와 요청/응답 DTO 추가(예외 시 null 반환 로직 포함) |
RestTemplate 설정 backend/src/main/java/moadong/global/config/RestTemplateConfig.java |
RestTemplate 빈(타임아웃 설정) 추가 |
컨트롤러·스타일링 backend/src/main/java/moadong/club/controller/ClubApplyAdminController.java |
import 와일드카드 및 포매팅 변경(기능 변경 없음) |
Sequence Diagram(s)
sequenceDiagram
participant User as 사용자
participant PublicService as ClubApplyPublicService
participant Repo as Repository
participant Publisher as ApplicantIdMessagePublisher
participant Rabbit as RabbitMQ
participant Consumer as ApplicantIdMessageConsumer
participant GemmaSvc as GemmaService
participant Gemma as Gemma Server
User->>PublicService: 지원서 제출 요청
PublicService->>Repo: ClubApplicant 저장
PublicService->>Publisher: addApplicantIdToQueue(appFormId, applicantId)
Publisher->>Rabbit: ApplicantSummaryMessage 발행
Rabbit-->>Consumer: 메시지 전달 (concurrency=1)
Consumer->>Repo: ClubApplicant, ApplicationForm 조회
Consumer->>Consumer: AESCipher로 답변 복호화 및 프롬프트 구성
Consumer->>GemmaSvc: getSummarizeContent(prompt)
GemmaSvc->>Gemma: POST /api/generate (AIRequest)
Gemma-->>GemmaSvc: AIResponse
alt 요약 성공
GemmaSvc-->>Consumer: 요약문
Consumer->>Repo: applicant.memo 업데이트 및 저장
else 요약 실패
GemmaSvc-->>Consumer: null 또는 오류
Consumer->>Rabbit: (재시도/DeadLetter) 메시지 재발행
end
Estimated code review effort
🎯 4 (Complex) | ⏱️ ~60 minutes
- 집중 검토 권장 파일/영역:
ApplicantIdMessageConsumer(AES 복호화, Gemma 요청/응답 처리, 오류·예외 흐름 및 재시도/DLX 동작)RabbitMQConfig(큐/바인딩/DLX 설정, MessageConverter, RabbitTemplate 기본 exchange/routing 설정)ClubApplyPublicService트랜잭션 경계와 퍼블리싱 타이밍(데이터 영속성 보장)GemmaService의 시간초과·예외 처리 및 null 반환 후 소비자 로직
Possibly related issues
- MOA-301: 동아리 지원이 들어올시 지원서의 내용을 요약한다 — PR이 메시지 큐 연동, Gemma AI 호출, 요약 저장 작업을 직접 구현해 해당 이슈의 체크리스트와 일치합니다.
- [feature] MOA-301 동아리 지원이 들어올시 지원서의 내용을 요약한다 #805 — 메시지-큐 기반 지원서 요약 기능(퍼블리셔/컨슈머/AI 연동)과 직접적으로 일치합니다.
Possibly related PRs
- PR
#579— RabbitMQ 구성, 메시지 DTO/퍼블리셔/컨슈머 및 Gemma 연동과 코드 레벨로 중복/연계되는 변경사항이 있음. - PR
#762— ClubApplicant 흐름 및 메시징 통합과 관련된 변경사항을 포함하여 연계 가능성이 높음. - PR
#406— 지원서 제출/서비스 레이어 변경과 연관되어 통합 영향 검토가 필요할 수 있음.
Suggested reviewers
- PororoAndFriends
- yw6938
Pre-merge checks and finishing touches
❌ Failed checks (1 warning)
| Check name | Status | Explanation | Resolution |
|---|---|---|---|
| Docstring Coverage | Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. | You can run @coderabbitai generate docstrings to improve docstring coverage. |
✅ Passed checks (5 passed)
| Check name | Status | Explanation |
|---|---|---|
| Description Check | ✅ Passed | Check skipped - CodeRabbit’s high-level summary is enabled. |
| Title check | ✅ Passed | PR 제목이 주요 변경사항과 완전히 관련되어 있습니다. '지원자의 지원서를 요약해서 메모에 추가한다'는 핵심 기능(AI 요약, 메모 저장)을 명확하게 설명합니다. |
| Linked Issues check | ✅ Passed | 코드 변경사항이 MOA-301의 모든 주요 목표를 충족합니다. MessageQueue 연동(RabbitMQ 설정, Publisher/Consumer 구현), AI 서버 연동(GemmaService, AIRequest/Response DTO), 지원서 입력 시 요약 저장(ClubApplyPublicService 개수)이 모두 구현되었습니다. |
| Out of Scope Changes check | ✅ Passed | ClubApplyAdminController의 import 정리 및 포맷팅 변경은 minor 스타일 정리로 범위 내입니다. 모든 코드 변경은 요약 기능 구현과 관련된 범위 내 작업입니다. |
| Description check | ✅ Passed | PR 목표가 명확하게 문서화되어 있습니다. RabbitMQ 선택 근거, 아키텍처 설계, 동기 처리 선택 이유 등이 상세히 설명되어 있고 문서와 다이어그램도 포함되어 있습니다. |
✨ Finishing touches
- 📝 Generate docstrings
🧪 Generate unit tests (beta)
- Create PR with unit tests
- Post copyable unit tests in a comment
- Commit unit tests in branch
feature/#805-summary-application-content-MOA-301
📜 Recent review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
Disabled knowledge base sources:
- Jira integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (1)
backend/src/main/java/moadong/club/service/ClubApplyPublicService.java(3 hunks)
🧰 Additional context used
🧠 Learnings (3)
📚 Learning: 2025-05-19T05:45:52.957Z
Learnt from: lepitaaar
Repo: Moadong/moadong PR: 406
File: backend/src/main/java/moadong/club/service/ClubApplyService.java:34-38
Timestamp: 2025-05-19T05:45:52.957Z
Learning: The code duplication between createClubApplication and editClubApplication methods in ClubApplyService.java is acknowledged but will be addressed in a future refactoring, as per the developer's plan.
Applied to files:
backend/src/main/java/moadong/club/service/ClubApplyPublicService.java
📚 Learning: 2025-09-30T05:26:41.788Z
Learnt from: alsdddk
Repo: Moadong/moadong PR: 765
File: backend/src/main/java/moadong/club/service/ClubApplyService.java:431-435
Timestamp: 2025-09-30T05:26:41.788Z
Learning: In the Moadong codebase's club application feature (backend/src/main/java/moadong/club/), multiple ClubApplicationForm entities can have ACTIVE status for the same clubId, semesterYear, and semesterTerm simultaneously. There is no uniqueness constraint requiring only one ACTIVE form per semester.
Applied to files:
backend/src/main/java/moadong/club/service/ClubApplyPublicService.java
📚 Learning: 2025-08-25T14:43:52.320Z
Learnt from: lepitaaar
Repo: Moadong/moadong PR: 703
File: backend/src/main/java/moadong/club/controller/ClubApplyController.java:84-84
Timestamp: 2025-08-25T14:43:52.320Z
Learning: In the Moadong codebase, questionId and clubId are equivalent identifiers that represent the same entity. The ClubApplicationRepository.findAllByIdInAndQuestionId method correctly uses clubId as the questionId parameter for filtering club applications.
Applied to files:
backend/src/main/java/moadong/club/service/ClubApplyPublicService.java
🧬 Code graph analysis (1)
backend/src/main/java/moadong/club/service/ClubApplyPublicService.java (2)
backend/src/main/java/moadong/club/service/ClubApplyAdminService.java (1)
Service(39-428)backend/src/main/java/moadong/club/service/ClubApplyServiceV1.java (1)
Service(31-202)
🔇 Additional comments (1)
backend/src/main/java/moadong/club/service/ClubApplyPublicService.java (1)
81-82: 변수명 변경이 의미를 명확히 합니다.
application에서applicant로 변수명을 변경한 것은 엔티티 타입(ClubApplicant)과 일관성을 높여 코드 가독성을 개선합니다.
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 @coderabbitai help to get the list of available commands and usage tips.
Test Results78 tests 75 ✅ 13s ⏱️ Results for commit 8ae47c0. ♻️ This comment has been updated with latest results. |
There was a problem hiding this comment.
Actionable comments posted: 7
🧹 Nitpick comments (4)
backend/src/main/java/moadong/club/controller/ClubApplyAdminController.java (1)
20-20: 와일드카드 import 사용에 대한 검토
org.springframework.web.bind.annotation.*와일드카드 import는 명시적인 의존성을 숨겨 코드 가독성을 저하시킬 수 있습니다. 명시적인 import 사용을 권장합니다.backend/src/main/java/moadong/club/service/ClubApplyAdminService.java (1)
6-6: 와일드카드 import 사용에 대한 검토
moadong.club.entity.*와일드카드 import는 명시적인 의존성을 숨겨 코드 가독성을 저하시킬 수 있습니다. 사용되는 엔티티들을 명시적으로 import하는 것을 권장합니다.backend/src/main/java/moadong/global/config/RestTemplateConfig.java (1)
13-19: 타임아웃 설정 확인 및 에러 핸들링 권장60초의 read timeout은 AI 요청 처리 시간(7-10초)에 대해 적절한 버퍼를 제공합니다. 그러나 네트워크 오류나 타임아웃 발생 시 처리를 위한 커스텀 에러 핸들러나 재시도 로직 추가를 고려하세요.
참고:
spring-retry의존성이 이미 프로젝트에 포함되어 있으므로 재시도 로직 적용을 검토할 수 있습니다.backend/src/main/java/moadong/global/config/RabbitMQConfig.java (1)
36-41: 연결 신뢰성 설정을 추가하세요.CachingConnectionFactory에 재연결 및 오류 처리 설정이 없어 RabbitMQ 서버 장애 시 복구가 어렵습니다.
다음 설정을 추가하는 것을 권장합니다:
@Bean public ConnectionFactory connectionFactory() { CachingConnectionFactory cf = new CachingConnectionFactory(host, port); cf.setUsername(username); cf.setPassword(password); + cf.setRequestedHeartBeat(30); // 연결 상태 확인 + cf.setConnectionTimeout(5000); // 연결 타임아웃 return cf; }또한 RabbitTemplate에 재시도 정책을 추가하는 것도 고려하세요:
@Bean public RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory) { RabbitTemplate template = new RabbitTemplate(connectionFactory); template.setMessageConverter(Jackson2JsonMessageConverter()); template.setMandatory(true); // 라우팅 실패 감지 template.setReturnsCallback(returned -> { log.error("Message returned: {}", returned.getMessage()); }); return template; }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
Disabled knowledge base sources:
- Jira integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (12)
backend/build.gradle(1 hunks)backend/src/main/java/moadong/club/controller/ClubApplyAdminController.java(3 hunks)backend/src/main/java/moadong/club/payload/dto/ApplicantSummaryMessage.java(1 hunks)backend/src/main/java/moadong/club/service/ClubApplyAdminService.java(1 hunks)backend/src/main/java/moadong/club/service/ClubApplyPublicService.java(3 hunks)backend/src/main/java/moadong/club/summary/ApplicantIdMessageConsumer.java(1 hunks)backend/src/main/java/moadong/club/summary/ApplicantIdMessagePublisher.java(1 hunks)backend/src/main/java/moadong/gemma/dto/AIRequest.java(1 hunks)backend/src/main/java/moadong/gemma/dto/AIResponse.java(1 hunks)backend/src/main/java/moadong/gemma/service/GemmaService.java(1 hunks)backend/src/main/java/moadong/global/config/RabbitMQConfig.java(1 hunks)backend/src/main/java/moadong/global/config/RestTemplateConfig.java(1 hunks)
🧰 Additional context used
🧠 Learnings (3)
📚 Learning: 2025-05-19T05:45:52.957Z
Learnt from: lepitaaar
Repo: Moadong/moadong PR: 406
File: backend/src/main/java/moadong/club/service/ClubApplyService.java:34-38
Timestamp: 2025-05-19T05:45:52.957Z
Learning: The code duplication between createClubApplication and editClubApplication methods in ClubApplyService.java is acknowledged but will be addressed in a future refactoring, as per the developer's plan.
Applied to files:
backend/src/main/java/moadong/club/service/ClubApplyPublicService.javabackend/src/main/java/moadong/club/controller/ClubApplyAdminController.javabackend/src/main/java/moadong/club/service/ClubApplyAdminService.java
📚 Learning: 2025-09-30T05:26:41.788Z
Learnt from: alsdddk
Repo: Moadong/moadong PR: 765
File: backend/src/main/java/moadong/club/service/ClubApplyService.java:431-435
Timestamp: 2025-09-30T05:26:41.788Z
Learning: In the Moadong codebase's club application feature (backend/src/main/java/moadong/club/), multiple ClubApplicationForm entities can have ACTIVE status for the same clubId, semesterYear, and semesterTerm simultaneously. There is no uniqueness constraint requiring only one ACTIVE form per semester.
Applied to files:
backend/src/main/java/moadong/club/service/ClubApplyPublicService.javabackend/src/main/java/moadong/club/controller/ClubApplyAdminController.javabackend/src/main/java/moadong/club/service/ClubApplyAdminService.java
📚 Learning: 2025-08-25T14:43:52.320Z
Learnt from: lepitaaar
Repo: Moadong/moadong PR: 703
File: backend/src/main/java/moadong/club/controller/ClubApplyController.java:84-84
Timestamp: 2025-08-25T14:43:52.320Z
Learning: In the Moadong codebase, questionId and clubId are equivalent identifiers that represent the same entity. The ClubApplicationRepository.findAllByIdInAndQuestionId method correctly uses clubId as the questionId parameter for filtering club applications.
Applied to files:
backend/src/main/java/moadong/club/service/ClubApplyPublicService.javabackend/src/main/java/moadong/club/controller/ClubApplyAdminController.javabackend/src/main/java/moadong/club/service/ClubApplyAdminService.java
🧬 Code graph analysis (3)
backend/src/main/java/moadong/club/summary/ApplicantIdMessageConsumer.java (1)
backend/src/main/java/moadong/club/summary/ApplicantIdMessagePublisher.java (1)
Component(9-25)
backend/src/main/java/moadong/club/service/ClubApplyAdminService.java (2)
backend/src/main/java/moadong/club/service/ClubApplyPublicService.java (1)
Service(29-126)backend/src/main/java/moadong/club/service/ClubApplyServiceV1.java (1)
Service(31-202)
backend/src/main/java/moadong/club/summary/ApplicantIdMessagePublisher.java (1)
backend/src/main/java/moadong/club/summary/ApplicantIdMessageConsumer.java (1)
Component(24-69)
⏰ 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: test
🔇 Additional comments (6)
backend/build.gradle (1)
36-36: RabbitMQ 의존성 추가 승인메시지 큐 기반 비동기 처리를 위한 AMQP 의존성 추가가 적절합니다. Spring Boot의 관리 버전을 사용하여 호환성이 보장됩니다.
backend/src/main/java/moadong/club/payload/dto/ApplicantSummaryMessage.java (1)
3-7: 메시지 DTO 구조 승인지원서 ID와 지원자 ID를 전달하는 간결하고 명확한 메시지 구조입니다. Java record 사용이 적절합니다.
backend/src/main/java/moadong/gemma/dto/AIResponse.java (1)
5-6: 응답 DTO 구조 승인AI 서버 응답을 매핑하는 간결한 구조입니다.
@JsonProperty사용이 적절합니다.backend/src/main/java/moadong/gemma/dto/AIRequest.java (1)
5-12: 요청 DTO 구조 승인Gemma API 요청을 위한 명확한 구조입니다.
@JsonProperty를 통한 snake_case 매핑이 적절합니다.backend/src/main/java/moadong/gemma/service/GemmaService.java (1)
26-39: 이중 역직렬화 로직 및 설정 외부화 검증 완료 - 부분 수정 필요검증 결과:
이중 역직렬화 문제 확인됨:
AIResponse는 단일 필드response (String)만 가지는 record입니다. Line 32의objectMapper.readValue(response.response(), AIResponse.class)는 String을 AIResponse로 파싱하려는 것으로, 논리적으로 타입 불일치 오류입니다. API 응답 구조를 재검토하고 수정이 필요합니다.모델명 하드코딩 확인됨: Line 31에서
"gemma3:4b"가 하드코딩되어 있으며, 프로퍼티 파일에 gemma 관련 설정이 없습니다. 외부화를 권장합니다.에러 핸들링 제안의 제약: 리뷰 제안의
ErrorCode.EXTERNAL_API_ERROR,ErrorCode.INTERNAL_SERVER_ERROR는 현재 codebase의 ErrorCode enum에 존재하지 않습니다. 기존 에러코드를 활용하거나 새로 추가한 후 적용하시기 바랍니다.에러 메시지 개선: "Json Serialize Error"는 네트워크 오류, 타임아웃, JSON 파싱 오류 등을 구분하지 못합니다. 더 명확한 메시지로 개선이 필요합니다.
권장 사항:
- Line 32의 이중 파싱 로직 제거 또는 API 응답 구조 재검토
- 모델명을 설정 파일로 외부화 (
@Value("${gemma.model.name:gemma3:4b}"))- null 반환 대신 예외 throw 또는 Optional 활용
- 적절한 에러코드 선택 후 예외 처리 개선
backend/src/main/java/moadong/club/summary/ApplicantIdMessagePublisher.java (1)
9-25: LGTM: 간결한 Publisher 구현RabbitTemplate을 사용한 메시지 발행 구현이 명확하고 간결합니다. Jackson2JsonMessageConverter를 통한 자동 직렬화도 적절하게 설정되어 있습니다.
오류 처리만 보완하면 production-ready한 코드가 될 것입니다.
backend/src/main/java/moadong/club/summary/ApplicantIdMessageConsumer.java
Show resolved
Hide resolved
backend/src/main/java/moadong/club/summary/ApplicantIdMessageConsumer.java
Show resolved
Hide resolved
backend/src/main/java/moadong/club/summary/ApplicantIdMessageConsumer.java
Show resolved
Hide resolved
backend/src/main/java/moadong/club/summary/ApplicantIdMessageConsumer.java
Outdated
Show resolved
Hide resolved
backend/src/main/java/moadong/club/summary/ApplicantIdMessagePublisher.java
Show resolved
Hide resolved
backend/src/main/java/moadong/global/config/RabbitMQConfig.java
Outdated
Show resolved
Hide resolved
|
수고하셨습니당 혹시 AI 모델을 백엔드 서버에 올리는게 어떤 부분이 힘든지 알수있을까요..? |
모델이 생각보다 무겁습니다 ㅋㅋㅋㅋㅋ.. 컴퓨팅파워가 부족해서 같이못올려요~ |
…-content-MOA-301' into feature/#805-summary-application-content-MOA-301
Zepelown
left a comment
There was a problem hiding this comment.
근데 확장성을 고려하면 서버단에서도 비동기 처리하는게 좋지 않을까요?
alsdddk
left a comment
There was a problem hiding this comment.
수고하셨어요! rabbitMQ와 다른 브로커들과의 비교까지 정리 잘해주셔서 쉽게 이해했습니다👍
나중에 재시도는 어떻게 처리하실지 기대됩니당
| public ResponseEntity<?> getClubApplications(@CurrentUser CustomUserDetails user, | ||
| @RequestParam(defaultValue = "agg") String mode) { //agg면 aggregation사용, server면, 서비스에서 그룹 및 정렬 | ||
| if("server".equalsIgnoreCase(mode)) { | ||
| if ("server".equalsIgnoreCase(mode)) { |
There was a problem hiding this comment.
꿀팁 한개 드리면 option + command + l 누르면 intellij 자동으로 포맷팅해줍니다 ㅇㅁㅇ
음 좀 비현실적인 부분인게, 확장이된다는건 AI 머신이 추가되는 경우밖에 없습니다. 그렇게 추가되는 경우 ai 서버단에 로드밸런서 + concurency 숫자만 늘리면 되기에 큰 문제 없을것으로 보입니다 |
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (5)
backend/src/main/java/moadong/club/controller/ClubApplyAdminController.java (1)
20-20: 포매팅 개선 사항을 확인했습니다.이 파일의 변경사항은 모두 스타일/포매팅 관련이며 기능적 변경은 없습니다:
- 와일드카드 import 사용 (line 20)
- import 순서 조정 (line 23)
- 파라미터 어노테이션 간격 조정 (line 39)
- if 문 뒤 공백 추가 (line 61) - 이전 리뷰 피드백 반영
기능적 영향이 없는 변경사항이므로 문제없습니다.
참고: 와일드카드 import(
import org.springframework.web.bind.annotation.*;)는 편리하지만, 명시적 import가 어떤 클래스를 사용하는지 더 명확하게 보여줍니다. 프로젝트 스타일 가이드가 허용한다면 현재 방식도 괜찮습니다.Also applies to: 23-23, 39-39, 61-61
backend/src/main/java/moadong/global/config/RabbitMQConfig.java (4)
92-97: ConnectionFactory에 resilience 설정을 추가하는 것을 권장합니다.현재 ConnectionFactory는 기본 연결 정보만 설정되어 있습니다. 프로덕션 환경에서 안정성을 위해 재시도, 타임아웃, heartbeat 설정을 추가하는 것을 권장합니다.
다음과 같이 설정을 보강할 수 있습니다:
@Bean public ConnectionFactory connectionFactory() { CachingConnectionFactory cf = new CachingConnectionFactory(host, port); cf.setUsername(username); cf.setPassword(password); + cf.setConnectionTimeout(30000); // 30초 연결 타임아웃 + cf.setRequestedHeartBeat(60); // 60초 heartbeat + cf.setChannelCacheSize(25); // 채널 캐시 크기 return cf; } }
77-84: RabbitTemplate에 에러 처리 설정을 추가하는 것을 권장합니다.현재 RabbitTemplate에 reply timeout이나 mandatory flag 설정이 없어, 메시지 라우팅 실패나 타임아웃 발생 시 이를 감지하기 어렵습니다.
다음과 같이 설정을 추가할 수 있습니다:
@Bean public RabbitTemplate applicantIdTemplate(ConnectionFactory connectionFactory) { RabbitTemplate template = new RabbitTemplate(connectionFactory); template.setMessageConverter(jackson2JsonMessageConverter()); template.setExchange(APPLICANT_ID_EXCHANGE_NAME); template.setRoutingKey(APPLICANT_ID_ROUTING_KEY); + template.setMandatory(true); // 라우팅 실패 시 returnCallback 호출 + template.setReplyTimeout(15000); // 15초 reply timeout + + // 라우팅 실패 로깅 + template.setReturnsCallback(returned -> { + log.error("Message returned: exchange={}, routingKey={}, replyText={}", + returned.getExchange(), returned.getRoutingKey(), returned.getReplyText()); + }); return template; }이를 통해 메시지 발행 실패를 조기에 감지하고 로깅할 수 있습니다.
62-64: Dead Letter Queue에 TTL 설정을 추가하는 것을 권장합니다.현재 DLQ에는 메시지 만료 정책이 없어 실패한 메시지가 무한정 축적됩니다. 오래된 실패 메시지를 정리하기 위해 TTL을 설정하는 것을 권장합니다.
다음과 같이 DLQ에 TTL을 추가할 수 있습니다:
@Bean public Queue deadLetterQueue() { - return new Queue(DEAD_LETTER_QUEUE_NAME, true); + return new Queue(DEAD_LETTER_QUEUE_NAME, true, false, false, + Map.of("x-message-ttl", 604800000) // 7일 후 자동 삭제 + ); }이를 통해 7일이 지난 실패 메시지는 자동으로 삭제되어 DLQ가 무한정 증가하지 않습니다.
23-35: Properties 검증 추가를 고려해보세요.주입된 properties에 대한 검증이 없어 누락 시 런타임에 불명확한 에러가 발생할 수 있습니다.
@Value대신@ConfigurationProperties를 사용하거나@PostConstruct에서 검증하는 것을 고려해보세요.예시:
@PostConstruct public void validate() { Objects.requireNonNull(host, "RabbitMQ host must be configured"); Objects.requireNonNull(username, "RabbitMQ username must be configured"); // ... 기타 필수 속성 검증 }또는
@ConfigurationProperties사용:@ConfigurationProperties(prefix = "spring.rabbitmq") @Validated public class RabbitMQProperties { @NotBlank private String host; @NotBlank private String username; // ... getters/setters }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
Disabled knowledge base sources:
- Jira integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (5)
backend/src/main/java/moadong/club/controller/ClubApplyAdminController.java(3 hunks)backend/src/main/java/moadong/club/service/ClubApplyAdminService.java(2 hunks)backend/src/main/java/moadong/club/summary/ApplicantIdMessageConsumer.java(1 hunks)backend/src/main/java/moadong/club/summary/ApplicantIdMessagePublisher.java(1 hunks)backend/src/main/java/moadong/global/config/RabbitMQConfig.java(1 hunks)
✅ Files skipped from review due to trivial changes (1)
- backend/src/main/java/moadong/club/service/ClubApplyAdminService.java
🚧 Files skipped from review as they are similar to previous changes (2)
- backend/src/main/java/moadong/club/summary/ApplicantIdMessagePublisher.java
- backend/src/main/java/moadong/club/summary/ApplicantIdMessageConsumer.java
🧰 Additional context used
🧠 Learnings (4)
📚 Learning: 2025-11-08T18:45:09.285Z
Learnt from: lepitaaar
Repo: Moadong/moadong PR: 809
File: backend/src/main/java/moadong/global/config/RabbitMQConfig.java:13-42
Timestamp: 2025-11-08T18:45:09.285Z
Learning: The Moadong project uses manually-configured RabbitMQ infrastructure (queues, exchanges, bindings) on the server side rather than declaring them as Spring beans in RabbitMQConfig. The configuration class only provides ConnectionFactory and RabbitTemplate beans.
Applied to files:
backend/src/main/java/moadong/global/config/RabbitMQConfig.java
📚 Learning: 2025-05-19T05:45:52.957Z
Learnt from: lepitaaar
Repo: Moadong/moadong PR: 406
File: backend/src/main/java/moadong/club/service/ClubApplyService.java:34-38
Timestamp: 2025-05-19T05:45:52.957Z
Learning: The code duplication between createClubApplication and editClubApplication methods in ClubApplyService.java is acknowledged but will be addressed in a future refactoring, as per the developer's plan.
Applied to files:
backend/src/main/java/moadong/club/controller/ClubApplyAdminController.java
📚 Learning: 2025-09-30T05:26:41.788Z
Learnt from: alsdddk
Repo: Moadong/moadong PR: 765
File: backend/src/main/java/moadong/club/service/ClubApplyService.java:431-435
Timestamp: 2025-09-30T05:26:41.788Z
Learning: In the Moadong codebase's club application feature (backend/src/main/java/moadong/club/), multiple ClubApplicationForm entities can have ACTIVE status for the same clubId, semesterYear, and semesterTerm simultaneously. There is no uniqueness constraint requiring only one ACTIVE form per semester.
Applied to files:
backend/src/main/java/moadong/club/controller/ClubApplyAdminController.java
📚 Learning: 2025-08-25T14:43:52.320Z
Learnt from: lepitaaar
Repo: Moadong/moadong PR: 703
File: backend/src/main/java/moadong/club/controller/ClubApplyController.java:84-84
Timestamp: 2025-08-25T14:43:52.320Z
Learning: In the Moadong codebase, questionId and clubId are equivalent identifiers that represent the same entity. The ClubApplicationRepository.findAllByIdInAndQuestionId method correctly uses clubId as the questionId parameter for filtering club applications.
Applied to files:
backend/src/main/java/moadong/club/controller/ClubApplyAdminController.java
🔇 Additional comments (1)
backend/src/main/java/moadong/global/config/RabbitMQConfig.java (1)
42-74: 수동 설정에서 코드 선언 방식으로 전환한 점 좋습니다!이전 리뷰에서 수동 설정 방식을 사용한다고 하셨는데, 이번에 Queue, Exchange, Binding을 Spring Bean으로 선언하는 방식으로 변경하신 점 excellent합니다. Infrastructure as Code 접근으로 환경별 일관성과 자동 프로비저닝이 가능해졌습니다.
Based on learnings
현재 dlx,dlq 도입해서 최대 3번까지 재시도후 실패시 dead queue로 이동시킵니다 |
#️⃣연관된 이슈
📝작업 내용
요약 테스트
RabbitMQ 도입
Message Queue를 왜 도입했을까요?
AI 모델을 백엔드 서버에 바로 올려서 사용할 수 있으면 참 좋겠지만....
사실 현실적으로 힘듭니다.
따라서 AI 모델(gemma3:4b)을 돌리는 서버와 백엔드 서버를 분리했습니다.
하지만 이또한 많은 지원서를 한번에 처리할 수 있는 컴퓨팅 파워가 되면 좋겠지만... ㅜㅜ
컴퓨팅파워가 부족하여 하나의 요약 요청에 7~10초 정도 소요되는... 무지막지한 처리속도를 보여줍니다
이러한 상황에 저희는 AI 요청의 수를 제한해야했고, 제한된만큼 한번의 처리에 담지못한 나머지 요청을 저장할 공간이필요했습니다.
그러한 요청을 저장할 수 있는 Queue 서비스 중 RabbitMQ를 선택하여 도입하게되었습니다.
도입 후 Service Architecture
처음 지원서가 들어올 시
applicationFormId와applicantId두개를 메시지에 담아publisher가 큐에 전송합니다.그 후
consumer가 해당 메시지를 receive 후 지원서의 질문과 내용을 조합해 prompt 를 생성 후 AI 서버로 동기 전송을 보냅니다.응답이 반환된 후 요약된 내용을 지원서의 메모에 추가하게됩니다.
장점
지금은
publisher와consumer가 한 백엔드 서버에 존재하지만 추후consumer만 다른 서비스로 분리하게 된다면,MSA아키텍쳐의 형태로 서버를 확장시킬 수 있게됩니다.왜 RabbitMQ일까?
RabbitMQ말고도 사용가능한 브로커는 많이 존재합니다.
대표적인 예시,
Redis,RabbitMQ,KafkaEtc..Redis pub/sub
redis에도 publisher, subscriber 라는 간단한 기능으로 메시지 브로커 기능이 존재합니다. 추가적인 브로커 서비스 배포 없이도 redis cloud에서 바로 시작할 수 있다는 점에서 채택할려 했으나,,..
In-Memory방식이라는 점에서 혹시나 redis서버가 재시작되었을때 큐에 저장된 내용이 휘발될 가능성이 존재했습니다.요약 요청이 누락된다해서 치명적인 문제가 야기되진않지만, 서비스 제공에서 누락될 수 있다는 점이 리스크로 다가왔습니다.
Kafka
사실상 메시지/이벤트 브로커중에선 하이엔드 브로커입니다. 하지만 저희서비스엔 하이엔드 브로커가 필요없었습니다.
Kafka를 이용한 병렬처리의 이점을 누리기엔 이미 AI 서버또한 하나였고, Kafka의 도입자체가 오버 엔지니어링이였습니다.
따라서, 간단하지만 높은 메시지 신뢰성을 가진 RabbitMQ를 사용하게되었습니다.
AI 요약 요청 왜 동기방식일까요?
AI 요약 요청부분에 비동기 HTTP 요청인

WebFlux가 아닌 동기 요청restTemplate가 쓰인것을 알 수 있습니다.당연히 비동기 요청으로 처리하는게 더 빠르고 좋은게 아닐까 생각을 할 수 있지만, 반복해서 말하듯 AI 서버에서 병목현상이 생기게됩니다.
<현재상황..>
어짜피 요약요청을 하나밖에 처리를 못하는 상황이니
RabbitMQ를 도입해 요청들을 모아둔것이고,Consumer가 하나의 쓰레드에서 개별적으로 요약요청을 전송해 처리 신뢰성과 구현 난이도를 챙겼습니다.중점적으로 리뷰받고 싶은 부분(선택)
논의하고 싶은 부분(선택)
🫡 참고사항
Summary by CodeRabbit
새로운 기능
잡무(Chores)