Skip to content

[feature] 지원자의 지원서를 요약해서 메모에 추가한다.#809

Merged
seongwon030 merged 17 commits intodevelop/befrom
feature/#805-summary-application-content-MOA-301
Nov 9, 2025
Merged

[feature] 지원자의 지원서를 요약해서 메모에 추가한다.#809
seongwon030 merged 17 commits intodevelop/befrom
feature/#805-summary-application-content-MOA-301

Conversation

@lepitaaar
Copy link
Contributor

@lepitaaar lepitaaar commented Nov 8, 2025

#️⃣연관된 이슈

#805

📝작업 내용

요약 테스트

스크린샷 2025-11-08 18 26 35

RabbitMQ 도입

Message Queue를 왜 도입했을까요?
AI 모델을 백엔드 서버에 바로 올려서 사용할 수 있으면 참 좋겠지만....
사실 현실적으로 힘듭니다.

따라서 AI 모델(gemma3:4b)을 돌리는 서버와 백엔드 서버를 분리했습니다.
하지만 이또한 많은 지원서를 한번에 처리할 수 있는 컴퓨팅 파워가 되면 좋겠지만... ㅜㅜ
컴퓨팅파워가 부족하여 하나의 요약 요청에 7~10초 정도 소요되는... 무지막지한 처리속도를 보여줍니다

이러한 상황에 저희는 AI 요청의 수를 제한해야했고, 제한된만큼 한번의 처리에 담지못한 나머지 요청을 저장할 공간이필요했습니다.
그러한 요청을 저장할 수 있는 Queue 서비스 중 RabbitMQ를 선택하여 도입하게되었습니다.

도입 후 Service Architecture

diagram

처음 지원서가 들어올 시 applicationFormIdapplicantId 두개를 메시지에 담아 publisher가 큐에 전송합니다.
그 후 consumer가 해당 메시지를 receive 후 지원서의 질문과 내용을 조합해 prompt 를 생성 후 AI 서버로 동기 전송을 보냅니다.
응답이 반환된 후 요약된 내용을 지원서의 메모에 추가하게됩니다.

장점

지금은 publisherconsumer가 한 백엔드 서버에 존재하지만 추후 consumer만 다른 서비스로 분리하게 된다면,
MSA 아키텍쳐의 형태로 서버를 확장시킬 수 있게됩니다.

왜 RabbitMQ일까?

RabbitMQ말고도 사용가능한 브로커는 많이 존재합니다.
대표적인 예시, Redis, RabbitMQ, Kafka Etc..

  1. Redis pub/sub
    redis에도 publisher, subscriber 라는 간단한 기능으로 메시지 브로커 기능이 존재합니다. 추가적인 브로커 서비스 배포 없이도 redis cloud에서 바로 시작할 수 있다는 점에서 채택할려 했으나,,..
    In-Memory 방식이라는 점에서 혹시나 redis서버가 재시작되었을때 큐에 저장된 내용이 휘발될 가능성이 존재했습니다.
    요약 요청이 누락된다해서 치명적인 문제가 야기되진않지만, 서비스 제공에서 누락될 수 있다는 점이 리스크로 다가왔습니다.

  2. Kafka
    사실상 메시지/이벤트 브로커중에선 하이엔드 브로커입니다. 하지만 저희서비스엔 하이엔드 브로커가 필요없었습니다.
    Kafka를 이용한 병렬처리의 이점을 누리기엔 이미 AI 서버또한 하나였고, Kafka의 도입자체가 오버 엔지니어링이였습니다.

따라서, 간단하지만 높은 메시지 신뢰성을 가진 RabbitMQ를 사용하게되었습니다.

AI 요약 요청 왜 동기방식일까요?

AI 요약 요청부분에 비동기 HTTP 요청인 WebFlux가 아닌 동기 요청 restTemplate가 쓰인것을 알 수 있습니다.
당연히 비동기 요청으로 처리하는게 더 빠르고 좋은게 아닐까 생각을 할 수 있지만, 반복해서 말하듯 AI 서버에서 병목현상이 생기게됩니다.
image
<현재상황..>

어짜피 요약요청을 하나밖에 처리를 못하는 상황이니 RabbitMQ를 도입해 요청들을 모아둔것이고, Consumer가 하나의 쓰레드에서 개별적으로 요약요청을 전송해 처리 신뢰성과 구현 난이도를 챙겼습니다.

중점적으로 리뷰받고 싶은 부분(선택)

리뷰어가 특별히 봐주었으면 하는 부분이 있다면 작성해주세요

ex) 메서드 XXX의 이름을 더 잘 짓고 싶은데 혹시 좋은 명칭이 있을까요?

논의하고 싶은 부분(선택)

논의하고 싶은 부분이 있다면 작성해주세요.

🫡 참고사항

Summary by CodeRabbit

  • 새로운 기능

    • 지원자 답변을 외부 AI 요약 서비스로 전송해 요약을 지원자 메모로 자동 저장
    • 지원자 아이디 기반의 비동기 요약 파이프라인 추가(메시지 발행/수신)
    • 외부 요약 서비스 연동용 요청/응답 DTO 및 클라이언트 서비스 추가
    • 메시지 발행·수신 컴포넌트와 요약 처리 소비자 추가
  • 잡무(Chores)

    • RabbitMQ 관련 설정 및 라이브러리 의존성 추가
    • HTTP 클라이언트 타임아웃 설정 추가

@lepitaaar lepitaaar self-assigned this Nov 8, 2025
@lepitaaar lepitaaar added ✨ Feature 기능 개발 💾 BE Backend labels Nov 8, 2025
@vercel
Copy link

vercel bot commented Nov 8, 2025

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Preview Comments Updated (UTC)
moadong Ready Ready Preview Comment Nov 9, 2025 0:48am

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Nov 8, 2025

Warning

.coderabbit.yaml has a parsing error

The CodeRabbit configuration file in this repository has a parsing error and default settings were used instead. Please fix the error(s) in the configuration file. You can initialize chat with CodeRabbit to get help with the configuration file.

💥 Parsing errors (1)
Validation error: Invalid regex pattern for base branch. Received: "**" at "reviews.auto_review.base_branches[0]"
⚙️ Configuration instructions
  • Please see the configuration documentation for more information.
  • You can also validate your configuration using the online YAML validator.
  • 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

Walkthrough

지원자 저장 시 RabbitMQ로 ID를 발행하고, 컨슈머가 메시지를 받아 AES 복호화한 답변을 Gemma AI에 요약 요청하여 결과를 applicant.memo에 저장하도록 RabbitMQ, 퍼블리셔/컨슈머, Gemma 연동 및 관련 설정(RestTemplate, RabbitMQConfig, DTOs)들을 추가합니다.

Changes

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
RabbitTemplateApplicantSummaryMessage 발행하는 컴포넌트 추가
컨슈머 및 요약 처리
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
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

  • 집중 검토 권장 파일/영역:
    • ApplicantIdMessageConsumer (AES 복호화, Gemma 요청/응답 처리, 오류·예외 흐름 및 재시도/DLX 동작)
    • RabbitMQConfig (큐/바인딩/DLX 설정, MessageConverter, RabbitTemplate 기본 exchange/routing 설정)
    • ClubApplyPublicService 트랜잭션 경계와 퍼블리싱 타이밍(데이터 영속성 보장)
    • GemmaService의 시간초과·예외 처리 및 null 반환 후 소비자 로직

Possibly related issues

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 ⚠️ Warning 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.

📥 Commits

Reviewing files that changed from the base of the PR and between 98af7d5 and 8ae47c0.

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

❤️ Share

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

@github-actions
Copy link

github-actions bot commented Nov 8, 2025

Test Results

78 tests   75 ✅  13s ⏱️
13 suites   3 💤
13 files     0 ❌

Results for commit 8ae47c0.

♻️ This comment has been updated with latest results.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 7

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

📥 Commits

Reviewing files that changed from the base of the PR and between e2cb5cf and 5b6dda3.

📒 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.java
  • backend/src/main/java/moadong/club/controller/ClubApplyAdminController.java
  • backend/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.java
  • backend/src/main/java/moadong/club/controller/ClubApplyAdminController.java
  • backend/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.java
  • backend/src/main/java/moadong/club/controller/ClubApplyAdminController.java
  • backend/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: 이중 역직렬화 로직 및 설정 외부화 검증 완료 - 부분 수정 필요

검증 결과:

  1. 이중 역직렬화 문제 확인됨: AIResponse는 단일 필드 response (String) 만 가지는 record입니다. Line 32의 objectMapper.readValue(response.response(), AIResponse.class)는 String을 AIResponse로 파싱하려는 것으로, 논리적으로 타입 불일치 오류입니다. API 응답 구조를 재검토하고 수정이 필요합니다.

  2. 모델명 하드코딩 확인됨: Line 31에서 "gemma3:4b"가 하드코딩되어 있으며, 프로퍼티 파일에 gemma 관련 설정이 없습니다. 외부화를 권장합니다.

  3. 에러 핸들링 제안의 제약: 리뷰 제안의 ErrorCode.EXTERNAL_API_ERROR, ErrorCode.INTERNAL_SERVER_ERROR는 현재 codebase의 ErrorCode enum에 존재하지 않습니다. 기존 에러코드를 활용하거나 새로 추가한 후 적용하시기 바랍니다.

  4. 에러 메시지 개선: "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한 코드가 될 것입니다.

@yw6938
Copy link
Collaborator

yw6938 commented Nov 9, 2025

수고하셨습니당 혹시 AI 모델을 백엔드 서버에 올리는게 어떤 부분이 힘든지 알수있을까요..?

@lepitaaar
Copy link
Contributor Author

수고하셨습니당 혹시 AI 모델을 백엔드 서버에 올리는게 어떤 부분이 힘든지 알수있을까요..?

모델이 생각보다 무겁습니다 ㅋㅋㅋㅋㅋ.. 컴퓨팅파워가 부족해서 같이못올려요~

Copy link
Member

@Zepelown Zepelown left a comment

Choose a reason for hiding this comment

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

근데 확장성을 고려하면 서버단에서도 비동기 처리하는게 좋지 않을까요?

Copy link
Collaborator

@alsdddk alsdddk left a comment

Choose a reason for hiding this comment

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

수고하셨어요! rabbitMQ와 다른 브로커들과의 비교까지 정리 잘해주셔서 쉽게 이해했습니다👍
나중에 재시도는 어떻게 처리하실지 기대됩니당

public ResponseEntity<?> getClubApplications(@CurrentUser CustomUserDetails user,
@RequestParam(defaultValue = "agg") String mode) { //agg면 aggregation사용, server면, 서비스에서 그룹 및 정렬
if("server".equalsIgnoreCase(mode)) {
if ("server".equalsIgnoreCase(mode)) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

앞으론 if 뒤에 띄어쓰기 잘 하겠습니다,,

Copy link
Contributor Author

Choose a reason for hiding this comment

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

꿀팁 한개 드리면 option + command + l 누르면 intellij 자동으로 포맷팅해줍니다 ㅇㅁㅇ

@lepitaaar
Copy link
Contributor Author

근데 확장성을 고려하면 서버단에서도 비동기 처리하는게 좋지 않을까요?

음 좀 비현실적인 부분인게, 확장이된다는건 AI 머신이 추가되는 경우밖에 없습니다. 그렇게 추가되는 경우 ai 서버단에 로드밸런서 + concurency 숫자만 늘리면 되기에 큰 문제 없을것으로 보입니다

Copy link
Contributor

@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

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

📥 Commits

Reviewing files that changed from the base of the PR and between 9e2561d and 98af7d5.

📒 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

@lepitaaar
Copy link
Contributor Author

수고하셨어요! rabbitMQ와 다른 브로커들과의 비교까지 정리 잘해주셔서 쉽게 이해했습니다👍 나중에 재시도는 어떻게 처리하실지 기대됩니당

현재 dlx,dlq 도입해서 최대 3번까지 재시도후 실패시 dead queue로 이동시킵니다

@seongwon030 seongwon030 merged commit a4a07bf into develop/be Nov 9, 2025
5 checks passed
@coderabbitai coderabbitai bot mentioned this pull request Nov 17, 2025
@lepitaaar lepitaaar deleted the feature/#805-summary-application-content-MOA-301 branch January 4, 2026 07:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

💾 BE Backend ✨ Feature 기능 개발

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants