Skip to content

refactor: 코드 리뷰 기반 책임 분리 및 도메인 중심 패키지 구조 전환#65

Merged
Woomin-Wang merged 6 commits intomainfrom
refactor/code-review
Apr 28, 2026
Merged

refactor: 코드 리뷰 기반 책임 분리 및 도메인 중심 패키지 구조 전환#65
Woomin-Wang merged 6 commits intomainfrom
refactor/code-review

Conversation

@Woomin-Wang
Copy link
Copy Markdown
Contributor

📌 관련 이슈 (Related Issue)


📝 작업 내용 (Description)

코드 리뷰를 통해 발견된 버그 2건을 수정하고, 레이어 전반의 책임 분리 및 패키지 구조 개편을 진행했습니다.

버그 수정

  • 화상 면접 세션 생성 시 totalQuestionCount가 요청값 기준으로 저장되던 문제를 실제 생성된 질문 수 기준으로 수정
  • 첫 답변 리워드 중복 지급 가능성을 조건부 UPDATE(updateLatestScoreIfFirstTime)로 원자적 처리하여 race condition 방지

컨트롤러 책임 분리

  • InterviewController 단일 컨트롤러를 AnswerController, QuestionController로 분리
  • 각 컨트롤러가 단일 도메인만 담당하도록 역할 명확화

서비스 책임 분리

  • AnswerService에서 DB 저장 로직을 AnswerPersistenceService로 추출
  • QuestionService에서 DB 저장 로직을 QuestionPersistenceService로 추출
  • SSE 구독 처리를 VideoAnswerSseService로 분리

패키지 구조 개편

  • 계층형(service/, entity/, dto/, repository/) → 도메인 중심(interview/answer/, interview/question/, common/, external/)으로
    전환
  • 클래스 네이밍 정비:
    • VideoAnswerAnalyzerVideoAnswerProcessor
    • VideoAnalysisServiceVideoFrameAnalysisService
    • VideoAnswerStreamServiceVideoAnswerSseService

🔄 변경 유형 (Type of Change)

  • ✨ 새로운 기능 (feat)
  • 🐛 버그 수정 (fix)
  • 📝 문서 수정 (docs)
  • 💄 스타일 (style)
  • ♻️ 리팩토링 (refactor)
  • ✅ 테스트 (test)
  • 🔧 기타 (chore)

✅ 체크리스트 (Checklist)

  • 코드가 정상적으로 동작하는지 확인했습니다
  • 기존 테스트가 통과합니다
  • 필요한 경우 새로운 테스트를 추가했습니다

💬 추가 코멘트 (Additional Comments)

  • 패키지 이동으로 인해 diff가 크게 보이지만 로직 변경은 버그 수정 2건과 책임 분리 리팩토링이 전부입니다.
  • docker-compose-dev.yml로 정상 기동 확인했습니다.

Copy link
Copy Markdown

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request focuses on a comprehensive package reorganization, migrating global components to common and external packages while grouping interview functionalities into domain-specific modules like answer, question, and jobposting. Key logic updates include refactoring the answer persistence layer to atomically verify the first daily answer and moving external service calls outside of database transactions. Review feedback suggests using a specific timezone (Asia/Seoul) for date-based logic to ensure consistency across environments and adding explicit cleanup for temporary video files to prevent resource leaks during processing errors.

}

private boolean isFirstTodayAnswer(final UUID questionId, final Integer score) {
LocalDateTime startOfDay = LocalDate.now().atStartOfDay();
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

LocalDate.now()는 시스템의 기본 시간대를 사용합니다. DailyQuestionGenerationService에서 Asia/Seoul 시간대를 명시적으로 사용하는 것과 일관성을 유지하기 위해, 여기서도 ZoneId.of("Asia/Seoul")를 사용하는 것이 권장됩니다. 서버 환경(예: UTC 설정된 컨테이너)에 따라 "오늘"의 기준이 달라져 최초 답변 판정 로직이 의도치 않게 동작할 위험이 있습니다.

Suggested change
LocalDateTime startOfDay = LocalDate.now().atStartOfDay();
LocalDateTime startOfDay = LocalDate.now(java.time.ZoneId.of("Asia/Seoul")).atStartOfDay();

Comment on lines +60 to +70
Path videoPath = createTempFile(video);
InterviewAnswer answer = answerPersistenceService.saveVideoAnswer(questionId, memberId);

// 3개 비동기 작업 모두 완료 시 이벤트 발행을 위한 트래커 초기화
completionTracker.init(answer.getId(), videoPath);
log.info("영상 답변 분석 시작 - questionId: {}, answerId: {}", questionId, answer.getId());

videoAnswerAnalyzer.uploadToS3(answer.getId(), videoPath, video.getContentType(), email);
videoAnswerAnalyzer.analyzeSTT(answer.getId(), questionId, memberId, videoPath);
videoAnswerAnalyzer.analyzeVideo(answer.getId(), videoPath);
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

createTempFile을 통해 생성된 임시 파일은 이후 DB 저장(saveVideoAnswer)이나 트래커 초기화(completionTracker.init) 과정에서 예외가 발생할 경우 삭제되지 않고 남을 수 있습니다. 비록 임시 파일이지만, 예외 발생 시 catch 블록에서 명시적으로 삭제하는 로직을 추가하여 디스크 자원 낭비를 방지하는 것이 좋습니다.

        Path videoPath = createTempFile(video);
        try {
            InterviewAnswer answer = answerPersistenceService.saveVideoAnswer(questionId, memberId);

            // 3개 비동기 작업 모두 완료 시 이벤트 발행을 위한 트래커 초기화
            completionTracker.init(answer.getId(), videoPath);
            log.info("영상 답변 분석 시작 - questionId: {}, answerId: {}", questionId, answer.getId());

            videoAnswerAnalyzer.uploadToS3(answer.getId(), videoPath, video.getContentType(), email);
            videoAnswerAnalyzer.analyzeSTT(answer.getId(), questionId, memberId, videoPath);
            videoAnswerAnalyzer.analyzeVideo(answer.getId(), videoPath);
        } catch (Exception e) {
            try {
                Files.deleteIfExists(videoPath);
            } catch (java.io.IOException ioe) {
                log.warn("임시 파일 삭제 실패 - path: {}", videoPath, ioe);
            }
            throw e;
        }
    }

@Woomin-Wang Woomin-Wang merged commit f99b72c into main Apr 28, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant