Conversation
- SimilarityCheckRequestDto: voteId, 원본/파생 이미지 경로 포함 - SimilarityCheckResponseDto: voteId, AI 판단 결과 포함
- 요청 큐, 익스체인지, 라우팅 키 추가 - 응답 큐, 익스체인지, 라우팅 키 추가 - 기존 AI 이미지 생성 패턴과 동일한 구조 적용
- 비동기로 RabbitMQ에 유사도 검사 요청 전송 - 투표 상태를 IN_PROGRESS로 업데이트 - 예외 발생 시 로깅 후 안전하게 종료 (비동기 메서드 특성)
- AI 서버 응답 수신 및 처리 - 판단 결과에 따라 승인/거부 가중치 추가 - 실패 시 투표를 CANCELLED 상태로 변경 - 투표 및 포스트 상태는 PENDING 유지 (사람 투표 대기)
- AiSimilarityRequestService 의존성 추가 - 원본 이미지 경로 조회 로직 추가 - TransactionSynchronization을 사용한 트랜잭션 커밋 후 비동기 실행 - 트랜잭션 타이밍 이슈 해결: DB 커밋 완료 후 비동기 메서드 호출
- AiSimilarityRequestServiceTest: 요청 전송, 실패 처리 테스트 - SimilarityCheckListenerTest: 응답 처리, 실패 시나리오 테스트 - AiDerivedPostServiceTest: 통합 테스트 업데이트 - 모든 테스트 케이스 통과 확인
WalkthroughRabbitMQ 토폴로지에 투표 유사도 요청/응답 큐·익스체인지·라우팅키를 추가하고, 유사도 요청/응답 DTO·AiSimilarityRequestService·SimilarityCheckListener를 신설했다. AiDerivedPostService는 커밋 후 트랜잭션 콜백으로 유사도 요청을 비동기로 발송하도록 연계되며 관련 단위·통합 테스트가 추가됐다. Changes
Sequence Diagram(s)sequenceDiagram
autonumber
participant Client as 클라이언트
participant PostSvc as AiDerivedPostService
participant DB as Repositories
participant TX as Tx(afterCommit)
participant ReqSvc as AiSimilarityRequestService
participant MQ as RabbitMQ
participant AISrv as AI 서버
participant Listener as SimilarityCheckListener
Client->>PostSvc: createAiDerivedPost(...)
PostSvc->>DB: 원본/파생 엔티티 생성 및 저장
PostSvc->>TX: afterCommit 등록 (유사도 요청)
Note right of TX: 트랜잭션 커밋 후 실행
TX->>ReqSvc: sendSimilarityCheckRequest(voteId, origPath, derPath)
ReqSvc->>DB: SimilarityVote 조회 및 상태 IN_PROGRESS 저장
ReqSvc->>MQ: publish SimilarityCheckRequestDto (exchange/routingKey)
MQ-->>AISrv: 요청 전달
AISrv-->>MQ: SimilarityCheckResponseDto(결정)
MQ-->>Listener: 메시지 수신
Listener->>DB: vote / summary 조회
alt decision == APPROVE or DENY
Listener->>DB: summary 가중치 추가, aiDecision 설정, summary 저장
else invalid/missing
Listener->>DB: vote 상태 CANCELLED 저장
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Suggested reviewers
Poem
Pre-merge checks and finishing touches❌ Failed checks (2 warnings)
✅ Passed checks (3 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 |
There was a problem hiding this comment.
Actionable comments posted: 2
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (9)
src/main/java/hanium/modic/backend/common/amqp/config/RabbitMqConfig.java(2 hunks)src/main/java/hanium/modic/backend/domain/post/service/AiDerivedPostService.java(4 hunks)src/main/java/hanium/modic/backend/domain/vote/dto/SimilarityCheckRequestDto.java(1 hunks)src/main/java/hanium/modic/backend/domain/vote/dto/SimilarityCheckResponseDto.java(1 hunks)src/main/java/hanium/modic/backend/domain/vote/listener/SimilarityCheckListener.java(1 hunks)src/main/java/hanium/modic/backend/domain/vote/service/AiSimilarityRequestService.java(1 hunks)src/test/java/hanium/modic/backend/domain/post/service/AiDerivedPostServiceTest.java(3 hunks)src/test/java/hanium/modic/backend/domain/vote/listener/SimilarityCheckListenerTest.java(1 hunks)src/test/java/hanium/modic/backend/domain/vote/service/AiSimilarityRequestServiceTest.java(1 hunks)
🧰 Additional context used
🧬 Code graph analysis (3)
src/test/java/hanium/modic/backend/domain/vote/listener/SimilarityCheckListenerTest.java (2)
src/test/java/hanium/modic/backend/domain/post/service/AiDerivedPostServiceTest.java (1)
ExtendWith(39-215)src/test/java/hanium/modic/backend/domain/vote/service/AiSimilarityRequestServiceTest.java (1)
ExtendWith(24-113)
src/test/java/hanium/modic/backend/domain/post/service/AiDerivedPostServiceTest.java (1)
src/test/java/hanium/modic/backend/domain/user/factory/UserFactory.java (1)
UserFactory(9-29)
src/test/java/hanium/modic/backend/domain/vote/service/AiSimilarityRequestServiceTest.java (1)
src/test/java/hanium/modic/backend/domain/post/service/AiDerivedPostServiceTest.java (1)
ExtendWith(39-215)
⏰ 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
| PostEntity originalPost = postEntityRepository.findById(createdAiImage.getPostId()) | ||
| .orElseThrow(() -> new AppException(ErrorCode.POST_NOT_FOUND_EXCEPTION)); | ||
| Long originalImageId = originalPost.getThumbnailImageId(); | ||
|
|
||
| // 원본 이미지 경로 조회 | ||
| PostImageEntity originalImage = postImageEntityRepository.findById(originalImageId) | ||
| .orElseThrow(() -> new AppException(ErrorCode.IMAGE_NOT_FOUND_EXCEPTION)); | ||
|
|
There was a problem hiding this comment.
원본 썸네일 ID null 처리 누락
originalPost.getThumbnailImageId()가 null일 수 있는 경우(예: 썸네일 미설정 포스트) findById(null) 호출로 InvalidDataAccessApiUsageException이 바로 발생합니다. 명시적으로 null 여부를 검증해 도메인 예외를 던지거나 기본 이미지를 처리해야 합니다.
다음과 같이 null 검사를 추가해 주세요:
- Long originalImageId = originalPost.getThumbnailImageId();
-
- // 원본 이미지 경로 조회
- PostImageEntity originalImage = postImageEntityRepository.findById(originalImageId)
+ Long originalImageId = originalPost.getThumbnailImageId();
+ if (originalImageId == null) {
+ throw new AppException(ErrorCode.IMAGE_NOT_FOUND_EXCEPTION);
+ }
+
+ // 원본 이미지 경로 조회
+ PostImageEntity originalImage = postImageEntityRepository.findById(originalImageId)
.orElseThrow(() -> new AppException(ErrorCode.IMAGE_NOT_FOUND_EXCEPTION));📝 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.
| PostEntity originalPost = postEntityRepository.findById(createdAiImage.getPostId()) | |
| .orElseThrow(() -> new AppException(ErrorCode.POST_NOT_FOUND_EXCEPTION)); | |
| Long originalImageId = originalPost.getThumbnailImageId(); | |
| // 원본 이미지 경로 조회 | |
| PostImageEntity originalImage = postImageEntityRepository.findById(originalImageId) | |
| .orElseThrow(() -> new AppException(ErrorCode.IMAGE_NOT_FOUND_EXCEPTION)); | |
| PostEntity originalPost = postEntityRepository.findById(createdAiImage.getPostId()) | |
| .orElseThrow(() -> new AppException(ErrorCode.POST_NOT_FOUND_EXCEPTION)); | |
| Long originalImageId = originalPost.getThumbnailImageId(); | |
| if (originalImageId == null) { | |
| throw new AppException(ErrorCode.IMAGE_NOT_FOUND_EXCEPTION); | |
| } | |
| // 원본 이미지 경로 조회 | |
| PostImageEntity originalImage = postImageEntityRepository.findById(originalImageId) | |
| .orElseThrow(() -> new AppException(ErrorCode.IMAGE_NOT_FOUND_EXCEPTION)); |
🤖 Prompt for AI Agents
In
src/main/java/hanium/modic/backend/domain/post/service/AiDerivedPostService.java
around lines 77 to 84, originalPost.getThumbnailImageId() may return null which
causes findById(null) to throw InvalidDataAccessApiUsageException; add an
explicit null check after retrieving originalPost and before calling
postImageEntityRepository.findById: if thumbnailImageId is null either throw a
domain AppException (e.g., ErrorCode.IMAGE_NOT_FOUND_EXCEPTION) or resolve a
default image id/path according to product rules, then proceed to load the image
entity only when the id is non-null.
| Optional<SimilarityVoteEntity> voteOpt = similarityVoteRepository.findById(voteId); | ||
| if (voteOpt.isEmpty()) { | ||
| log.error("[유사도 검사 요청 실패] 투표를 찾을 수 없습니다. voteId={}", voteId); | ||
| return; | ||
| } |
There was a problem hiding this comment.
트랜잭션 커밋 전에 조회되어 요청이 바로 무시됩니다
@Async 덕분에 이 메서드는 호출 직후 별도 스레드에서 실행되는데, 호출자는 (예: AiDerivedPostService#createAiDerivedPost) 동일 트랜잭션 안에서 SimilarityVoteEntity와 요약을 Persist 한 직후 여기로 위임합니다. 호출자의 트랜잭션이 아직 커밋되지 않은 상태에서 Line 41의 findById가 실행되면 대부분 Optional.empty()가 돌아오고, 현재 구현은 단순 로그 후 return 하기 때문에 큐로 아무 메시지도 나가지 않습니다. 결과적으로 AI 유사도 검사가 전혀 수행되지 않는 치명적인 경로입니다.
커밋 이후에만 이 로직이 돌도록 보장해 주세요. 예컨대 호출부에서 TransactionSynchronizationManager.registerSynchronization(... afterCommit ...)로 이 메서드를 스케줄하거나, 여기서 직접 TransactionSynchronization/@TransactionalEventListener(phase = AFTER_COMMIT)을 활용해 커밋 완료 이후에 메시지를 발행하도록 구조를 바꿔야 합니다. 그 과정에서 상태 업데이트도 커밋 전에 끝내고, 발행은 afterCommit 블록 안에서 수행하도록 옮기면 됩니다.
🤖 Prompt for AI Agents
In
src/main/java/hanium/modic/backend/domain/vote/service/AiSimilarityRequestService.java
around lines 41-45, the async method queries the vote before the caller's
transaction may have committed and returns when findById yields empty; change
the flow so publication happens only after the creating transaction commits.
Keep any state updates and persistence in the original transaction, and move the
message/publication logic into an after-commit hook — either by having the
caller register TransactionSynchronizationManager.registerSynchronization(...
afterCommit ...) to invoke this class, or by refactoring this service to emit an
event handled with @TransactionalEventListener(phase =
TransactionPhase.AFTER_COMMIT); ensure the after-commit handler re-fetches the
entity (or uses an ID) and publishes the AI similarity request, while preserving
existing error/logging behavior.
- AiDerivedPostServiceTest에 TransactionSynchronizationManager 모킹 추가 - MockedStatic을 사용하여 트랜잭션 커밋 후 콜백 즉시 실행 처리 - AiDerivedPostControllerIntegrationTest 통합 테스트 수정 - PostEntity 저장 전 PostImageEntity 생성하여 thumbnailImageId 제약조건 해결 - 원본 이미지 경로 조회를 위한 테스트 데이터 구조 개선 Co-Authored-By: Claude <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 0
🧹 Nitpick comments (1)
src/test/java/hanium/modic/backend/domain/post/service/AiDerivedPostServiceTest.java (1)
164-168: AI 유사도 요청 검증 강화 필요
anyLong()으로 voteId를 허용하면 서비스가 잘못된 ID를 넘겨도 테스트가 통과하므로 회귀를 잡아내지 못합니다.eq(mockSavedVote.getId())또는 ArgumentCaptor를 사용해 저장된 투표 ID가 그대로 전달되는지 검증하도록 좁혀 주세요.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
src/test/java/hanium/modic/backend/domain/post/service/AiDerivedPostServiceTest.java(2 hunks)src/test/java/hanium/modic/backend/web/post/controller/AiDerivedPostControllerIntegrationTest.java(2 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
src/test/java/hanium/modic/backend/domain/post/service/AiDerivedPostServiceTest.java (2)
src/test/java/hanium/modic/backend/domain/vote/service/AiSimilarityRequestServiceTest.java (1)
ExtendWith(24-113)src/test/java/hanium/modic/backend/domain/user/factory/UserFactory.java (1)
UserFactory(9-29)
⏰ 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
개요
AI 파생 포스트 생성 시 원본과 파생 이미지의 유사도를 AI 서버에 요청하여 자동으로 판단하는 기능을 RabbitMQ 기반 비동기로 구현했습니다.
주요 변경사항
1. DTO 추가
SimilarityCheckRequestDto: AI 서버로 전송할 유사도 검사 요청SimilarityCheckResponseDto: AI 서버로부터 받을 유사도 검사 응답2. RabbitMQ 설정
3. 비동기 처리 서비스
AiSimilarityRequestService: RabbitMQ로 유사도 검사 요청 전송4. 응답 리스너
SimilarityCheckListener: AI 서버 응답 수신 및 처리5. 트랜잭션 동기화
TransactionSynchronization을 사용하여 트랜잭션 커밋 후 비동기 실행테스트
AiSimilarityRequestServiceTest: 요청 전송 및 실패 처리 테스트SimilarityCheckListenerTest: 응답 처리 및 다양한 실패 시나리오 테스트AiDerivedPostServiceTest: 통합 테스트 업데이트Closes #202
Summary by CodeRabbit
New Features
Tests