[REFACTOR] 로그인 성공 이후 로직 개선-비동기 중앙 관리, 비동기 관련 트랜잭션 개선, 비즈니스 로직 코드 분리#304
[REFACTOR] 로그인 성공 이후 로직 개선-비동기 중앙 관리, 비동기 관련 트랜잭션 개선, 비즈니스 로직 코드 분리#304yujin9907 wants to merge 13 commits into
Conversation
- `ReportServiceImpl`에서 `KafkaTemplate` 직접 사용을 제거하고 `KafkaMessageProducer` 추상화 계층 도입 - `TransactionalEventListener`를 통해 DB 트랜잭션 커밋 후에만 Kafka 메시지가 전송되도록 변경하여 데이터 일관성 보장 - Kafka 메시지 전송 실패 시 해당 태스크 스텝을 `FAILED`로 처리하는 로직 추가 - `ReportStep` enum 도입 및 Kafka 메시지 DTO를 전용 패키지로 이동하여 타입 안정성과 모듈성 개선 IssueNum #298
- 원자성 문제 : Kafka 메시지 전송 실패 시, Task 상태 원자적 업데이트 - Kafka 메시지 처리 실패 시 사용자에게 실시간 알림을 보낼 수 있도록 Redis Pub/Sub 기반 알림 기능 추가 IssueNum #298
- Kafka 메시지 발행 리스너(`handleReportKafkaEvent`)에 `@Async` 어노테이션을 추가하여 비동기로 동작하도록 변경 - 트랜잭션 커밋 후 Kafka 메시지 전송이 메인 스레드를 블로킹하지 않도록 하여 애플리케이션 응답성 향상 - 브로커 서버가 꺼졌을 때 :`kafkaTemplate.send` 호출 시 발생할 수 있는 즉각적인 예외(`try-catch`)와 비동기 콜백 예외(`whenComplete`)를 모두 `handleFailure`에서 처리하도록 개선하여 실패 처리의 견고성 강화 IssueNum #298
…ecture-refactoring [REFACTOR] 리포트 생성 아키텍처를 Kafka 기반 비동기 구조로 리팩토링(A파트)
- 로그인 테스트 코드 추가 - 비동기 채널 동기화 로직 검증을 위한 슬라이스 테스트(`ChannelSyncServiceAsyncTest`) 등 주요 로직에 대한 테스트 커버리지 확대 - 안정적인 테스트 환경 구축을 위해 Testcontainers (PostgreSQL) 및 Embedded Redis 의존성 추가 및 설정 - 통합 테스트를 위한 `IntegrationTestSupport` 기반 클래스 도입 IssueNum #303
- 기존 채널 생성 및 업데이트 로직을 채널 기본 정보, 영상, 통계 업데이트 3단계로 분리 - YouTube API 호출(비트랜잭션)과 DB 저장(트랜잭션) 로직의 경계를 명확히 구분 IssueNum #303
- VideoSyncService: 영상 동기화 - ChannelStatsService: 통계 갱신 - ChannelServiceImpl (채널 및 퍼사드 위임) #303
- `LoginPostProcessor`에서 비동기 실행을 제어하도록 함
- 새 로그인 플로우
1. 구글 토큰 추출
2. processLogin() (멤버 찾기/생성 + 탈퇴 복구)
3. createOrGetBasicChannel() 기본 채널 조회/생성 (동기, API 최대 1회)
4. JWT → redirect
5. loginPostProcessor.executeAsync() 비동기 후처리
├─ syncVideos (기존 사용자만)
├─ updateStats (기존 사용자만)
├─ trendKeyword
└─ cleanupIdeas
#303
로그인 후처리 로직 분리에 맞춰 테스트 구조 및 검증 방식 변경 - `MemberOauth2UserServiceTest`에서 채널 관련 로직을 제거하고 멤버 로그인 처리에 집중 - `Oauth2LoginSuccessHandlerTest`에서 `LoginPostProcessor`를 사용한 비동기 후처리 검증 추가 - `MemberOauth2UserService`의 `processLogin` 메서드가 순수하게 멤버 생성/조회/복구 역할만 수행하도록 테스트 범위 조정 IssueNum #303
## 변경 배경 코드리뷰에서 지적된 세 가지 문제를 수정한다. 1. YoutubeUtil 정적 유틸 클래스 - 테스트 불가, DI 불가 2. @transactional self-invocation 문제 - 프록시 우회로 트랜잭션 미적용 3. Oauth2LoginSuccessHandler URL 조립 로직 중복 ## 주요 변경 ### YouTubeApiService 도입 (YoutubeUtil 대체) - YoutubeUtil 정적 메서드를 YouTubeApiService 빈으로 래핑 - fetchChannelDetails / fetchVideoBriefs / fetchVideoDetails / fetchAllVideoShares / isYoutubeShorts / markShortsVideos 메서드 제공 - ChannelServiceImpl, ChannelStatsService, VideoSyncService에서 DI 주입으로 교체 - 테스트에서 MockedStatic<YoutubeUtil> 제거 → 일반 @mock으로 단순화 ### Self-invocation 트랜잭션 경계 수정 - ChannelServiceImpl.createOrGetBasicChannel: @transactional → Propagation.NOT_SUPPORTED - YouTube API 호출(외부 I/O)을 트랜잭션 안에 묶지 않기 위함 - ChannelStatsService.updateChannelStats: @transactional → TransactionTemplate - YouTube API 호출(트랜잭션 밖) → DB 저장(트랜잭션 안) 순서를 명시적으로 분리 ### VideoServiceImpl.saveVideosWithStats 추출 - VideoSyncService에서 영상 저장·통계 누산 로직을 VideoServiceImpl로 이동 - findByYoutubeVideoIdIn 배치 조회로 N+1 문제 방지 - VideoService 인터페이스에 메서드 추가 ### LoginPostProcessor / AsyncConfig - @async → @async("asyncExecutor") 명시적 스레드풀 지정 - AsyncConfig에 asyncExecutor 빈 등록 ### Oauth2LoginSuccessHandler 가독성 개선 - extractGoogleAccessToken / buildSuccessRedirectUrl / buildErrorRedirectUrl private 메서드 추출 - 중복 URL 조립 코드 제거 #303
- VideoServiceImpl.saveVideosWithStats — @async 경계 detached Channel findById 재조회 - ChannelStatsService.updateChannelStats — @async 경계를 넘은 detached Channel을 TransactionTemplate 내 findById로 재조회 후 업데이트 (PersistentObjectException 방지) - ChannelStatsService — RuntimeException → ChannelHandler(_CHANNEL_NOT_FOUND) 통일 - VideoService — 미사용 @transactional import 제거 - VideoSyncServiceTest — 무의미한 isYoutubeShorts 테스트 삭제 - Oauth2LoginSuccessHandler — loginPostProcessor.executeAsync에 isChannelNew(채널 신규 여부) 전달. 기존 isNew(멤버 신규 여부)는 redirect URL에만 유지하여 중복 YouTube API 호출 방지 - AsyncConfig.shortsCheckExecutor — executor 반환(라이프사이클 편입) #303
🤖 Gemini AI 코드 리뷰❌ Gemini API 호출 실패: 최대 재시도 횟수 초과 이 리뷰는 Gemini AI가 자동으로 생성했습니다. 참고용으로만 활용해주세요. |
There was a problem hiding this comment.
Code Review
This pull request refactors the login and channel synchronization flow to improve performance by moving heavy YouTube API interactions and post-login tasks to an asynchronous processor (LoginPostProcessor) and a dedicated API service (YouTubeApiService). The changes also include the addition of Testcontainers and Embedded Redis for more robust integration testing. Review feedback suggests removing an unused import in the new processor and renaming a parameter to better distinguish between member and channel status.
| import channeling.be.domain.idea.application.IdeaService; | ||
| import channeling.be.domain.member.domain.Member; | ||
| import channeling.be.domain.video.application.VideoSyncService; | ||
| import channeling.be.global.infrastructure.redis.RedisUtil; |
| public void executeAsync(Member member, Channel channel, | ||
| String googleAccessToken, boolean isNew) { | ||
| tryStep(STEP_SYNC_VIDEOS, member.getId(), () -> | ||
| videoSyncService.syncVideos(channel, googleAccessToken)); | ||
|
|
||
| if (!isNew) { | ||
| tryStep(STEP_UPDATE_STATS, member.getId(), () -> | ||
| channelStatsService.updateChannelStats(channel, googleAccessToken)); | ||
| } | ||
|
|
||
| tryStep(STEP_TREND_KEYWORD, member.getId(), () -> | ||
| trendKeywordService.updateChannelTrendKeyword(member)); | ||
|
|
||
| tryStep(STEP_CLEANUP_IDEAS, member.getId(), () -> | ||
| ideaService.deleteNotBookMarkedIdeas(member)); | ||
| } |
There was a problem hiding this comment.
executeAsync 메소드의 isNew 파라미터 이름이 역할에 비해 다소 모호하게 느껴집니다. 이 파라미터는 Oauth2LoginSuccessHandler에서 isChannelNew 값으로 호출되며, 채널의 신규 생성 여부를 나타냅니다. 반면, 로그인 흐름의 다른 부분에서는 isNew가 주로 멤버(사용자)의 신규 여부를 가리키는 변수명으로 사용되고 있어 혼동의 여지가 있습니다. 코드의 명확성을 높이고 오해를 방지하기 위해 파라미터 이름을 isChannelNew로 변경하는 것을 제안합니다.
| public void executeAsync(Member member, Channel channel, | |
| String googleAccessToken, boolean isNew) { | |
| tryStep(STEP_SYNC_VIDEOS, member.getId(), () -> | |
| videoSyncService.syncVideos(channel, googleAccessToken)); | |
| if (!isNew) { | |
| tryStep(STEP_UPDATE_STATS, member.getId(), () -> | |
| channelStatsService.updateChannelStats(channel, googleAccessToken)); | |
| } | |
| tryStep(STEP_TREND_KEYWORD, member.getId(), () -> | |
| trendKeywordService.updateChannelTrendKeyword(member)); | |
| tryStep(STEP_CLEANUP_IDEAS, member.getId(), () -> | |
| ideaService.deleteNotBookMarkedIdeas(member)); | |
| } | |
| public void executeAsync(Member member, Channel channel, | |
| String googleAccessToken, boolean isChannelNew) { | |
| tryStep(STEP_SYNC_VIDEOS, member.getId(), () -> | |
| videoSyncService.syncVideos(channel, googleAccessToken)); | |
| if (!isChannelNew) { | |
| tryStep(STEP_UPDATE_STATS, member.getId(), () -> | |
| channelStatsService.updateChannelStats(channel, googleAccessToken)); | |
| } | |
| tryStep(STEP_TREND_KEYWORD, member.getId(), () -> | |
| trendKeywordService.updateChannelTrendKeyword(member)); | |
| tryStep(STEP_CLEANUP_IDEAS, member.getId(), () -> | |
| ideaService.deleteNotBookMarkedIdeas(member)); | |
| } |
PR 제목
[Feat/Fix/Refactor/Docs/Chore 등]: 간결하게 변경 내용을 요약해주세요. (예: Feat: 사용자 로그인 기능 구현)
운영 반영은 보류하고 리뷰 받으려고 올렸슴니다
1. 로그인 성공 이후 비동기 처리 작업 processor 추가
2. 트랜잭션 확인(YouTube API 호출이 트랜잭션 안에서 실행 → DB 커넥션 장시간 점유 문제)
(1) 트랜잭션과 외부 호출 로직 분리
(2) 추가로 @transactional Propagation 옵션을 통해 youtube service 관련 메서드들은 NEVER 로, 트랜잭션 내에서 호출하지 못 하도록 강제함
(3) Self-invocation 확인함
3. 테스트 코드 작성
정리해야 됨!
4. 비즈니스 로직 코드 분리
ChannelServiceImpl(모놀리식)ChannelServiceImplVideoSyncServiceChannelStatsServiceYouTubeApiServiceChannelSyncServiceVideoSyncService로 대체ChannelConverter(일부)투두
먼저 반영 후 하나씩 테스트
(1) 유튜브 유틸 (static) -> 유튜브 서비스(컴포넌트) 로 옮기기
(2) 로그인 성공 핸들러 더 간소화
(3) 로그인 응답 방식 변경
(4) 유튜브 N+1 호출(쇼츠 구분) 개선
(5) osiv false