70 KiB
메인 홈 추천 API Implementation Plan
For agentic workers: REQUIRED SUB-SKILL: Use
superpowers:subagent-driven-development또는superpowers:executing-plans로 task 단위 구현을 진행한다. 각 단계는 체크박스(- [ ])로 진행 상태를 갱신한다.
Goal: /api/v2/home/recommendations 하위에 메인 홈 추천 통합 조회, 섹션별 전체보기, 콘텐츠 조회 이력 기록, 추천 크리에이터 동시 팔로우 API를 제공한다.
Architecture: 공개 API 조립은 kr.co.vividnext.sodalive.v2.api.home에 두고, 추천 정책/점수/스냅샷/조회 이력/팔로우 기능은 kr.co.vividnext.sodalive.v2.recommend에 둔다. v2.api.home은 v2.recommend의 application use case만 호출하며, v2.recommend는 API DTO에 의존하지 않는다.
Tech Stack: Kotlin, Spring Boot 2.7.14, Java 17, Spring Data JPA, QueryDSL, native SQL, JUnit 5, Gradle Wrapper
0. 구현 전 확정 사항
- 통합 조회:
GET /api/v2/home/recommendations - 전체보기 조회:
GET /api/v2/home/recommendations/livesGET /api/v2/home/recommendations/debut-creatorsGET /api/v2/home/recommendations/first-audio-contentsGET /api/v2/home/recommendations/ai-charactersGET /api/v2/home/recommendations/communities
- 추천 크리에이터 동시 팔로우:
POST /api/v2/home/recommendations/creators/follow- 요청에는
creatorIds만 포함한다. - 장르의 크리에이터와 최근 응원이 많은 크리에이터는 동일한 id 리스트 검증/팔로우 저장 로직을 사용한다.
- 요청에는
- 페이징 방식: 기존 Spring
Pageable을 우선 사용하고 응답에는items,page,size,hasNext를 포함한다. - 시간 응답:
LocalDateTime저장값을 기존 관례처럼 KST 기준 저장값으로 보고 UTC ISO 문자열(...Z)로 변환한다. - 스냅샷 일 배치는 KST 매일 06:00:00에 실행하고, 스냅샷 기준 시각은 전날 23:59:59 KST 의미를 코드에서 명확히 계산한다. 스케줄러는
@Scheduled(cron = "0 0 6 * * *", zone = "Asia/Seoul")로 등록한다. - 저장소에는 DB migration 디렉터리가 없으므로 신규 스냅샷/조회 이력 엔티티 추가 시 운영 DB DDL 반영은 배포 절차에서 별도 수행한다. 코드 구현 task에는 JPA 엔티티/리포지토리와 통합 테스트를 포함하고, Phase 7 완료 후 신규 엔티티 테이블 생성 SQL을 문서 산출물로 작성한다.
- 조회 구현은 JPA/QueryDSL 우선, native SQL 제한 사용의 하이브리드 전략으로 진행한다. 단순 조회/상세 조립/대상 활성 조건은 JPA 또는 QueryDSL로 표현하고, CTE/window function/
union all/DB-side exact scoring처럼 SQL 고급 기능이 필요한 추천 산정에만 native SQL을 사용한다. native SQL 사용 시에는 H2 MySQL mode와 Kotlin 정책 산식 parity를 포함한 repository 통합 테스트를 반드시 둔다. - 이번 범위에서는 기존 홈/콘텐츠 홈/라이브/AI 캐릭터 API의 공개 스키마를 변경하지 않고, 앱 다국어 문구 번역, ML 개인화, A/B 테스트 플랫폼, 관리자 화면, 추천 결과 수동 편집 기능은 구현하지 않는다. 응답 enum은 앱 다국어 처리를 위해 안정적인 영문 code로 유지한다.
1. 파일 구조 계획
신규 API 조립 계층
- Create:
src/main/kotlin/kr/co/vividnext/sodalive/v2/api/home/adapter/in/web/HomeRecommendationController.kt - Create:
src/main/kotlin/kr/co/vividnext/sodalive/v2/api/home/application/HomeRecommendationFacade.kt - Create:
src/main/kotlin/kr/co/vividnext/sodalive/v2/api/home/dto/HomeRecommendationResponse.kt - Create:
src/main/kotlin/kr/co/vividnext/sodalive/v2/api/home/dto/HomeRecommendationPageResponse.kt - Create:
src/main/kotlin/kr/co/vividnext/sodalive/v2/api/home/dto/FollowRecommendedCreatorsRequest.kt - Create:
src/main/kotlin/kr/co/vividnext/sodalive/v2/api/home/dto/FollowRecommendedCreatorsResponse.kt
신규 추천 기능 계층
- Create:
src/main/kotlin/kr/co/vividnext/sodalive/v2/recommend/domain/RecommendationScorePolicy.kt - Create:
src/main/kotlin/kr/co/vividnext/sodalive/v2/recommend/domain/CreatorDebutPolicy.kt - Create:
src/main/kotlin/kr/co/vividnext/sodalive/v2/recommend/domain/RecommendedActivityType.kt - Create:
src/main/kotlin/kr/co/vividnext/sodalive/v2/recommend/domain/RecommendedSectionType.kt - Create:
src/main/kotlin/kr/co/vividnext/sodalive/v2/recommend/application/HomeRecommendationQueryService.kt - Create:
src/main/kotlin/kr/co/vividnext/sodalive/v2/recommend/application/CreatorContentViewHistoryService.kt - Create:
src/main/kotlin/kr/co/vividnext/sodalive/v2/recommend/application/RecommendedCreatorFollowService.kt - Create:
src/main/kotlin/kr/co/vividnext/sodalive/v2/recommend/application/RecommendationSnapshotRefreshService.kt - Create:
src/main/kotlin/kr/co/vividnext/sodalive/v2/recommend/port/out/HomeRecommendationQueryPort.kt - Create:
src/main/kotlin/kr/co/vividnext/sodalive/v2/recommend/port/out/CreatorContentViewHistoryPort.kt - Create:
src/main/kotlin/kr/co/vividnext/sodalive/v2/recommend/port/out/RecommendationSnapshotPort.kt - Create:
src/main/kotlin/kr/co/vividnext/sodalive/v2/recommend/adapter/out/persistence/HomeRecommendationQueryRepository.kt - Create:
src/main/kotlin/kr/co/vividnext/sodalive/v2/recommend/adapter/out/persistence/DefaultHomeRecommendationQueryRepository.kt - Create:
src/main/kotlin/kr/co/vividnext/sodalive/v2/recommend/adapter/out/persistence/CreatorContentViewHistory.kt - Create:
src/main/kotlin/kr/co/vividnext/sodalive/v2/recommend/adapter/out/persistence/CreatorContentViewHistoryRepository.kt - Create:
src/main/kotlin/kr/co/vividnext/sodalive/v2/recommend/adapter/out/persistence/RecommendationSnapshot.kt - Create:
src/main/kotlin/kr/co/vividnext/sodalive/v2/recommend/adapter/out/persistence/RecommendationSnapshotRepository.kt - Create:
src/main/kotlin/kr/co/vividnext/sodalive/v2/recommend/adapter/out/scheduler/RecommendationSnapshotScheduler.kt
기존 코드 연결
- Modify:
src/main/kotlin/kr/co/vividnext/sodalive/content/AudioContentService.kt- 콘텐츠 상세 조회 성공 시
CreatorContentViewHistoryService.recordView(...)를 호출한다.
- 콘텐츠 상세 조회 성공 시
- Modify:
src/main/kotlin/kr/co/vividnext/sodalive/content/AudioContentController.kt- 기존 공개 스키마는 유지하고 인증 회원 정보를 서비스로 전달하는 기존 흐름만 활용한다.
테스트
- Create:
src/test/kotlin/kr/co/vividnext/sodalive/v2/recommend/domain/RecommendationScorePolicyTest.kt - Create:
src/test/kotlin/kr/co/vividnext/sodalive/v2/recommend/domain/CreatorDebutPolicyTest.kt - Create:
src/test/kotlin/kr/co/vividnext/sodalive/v2/recommend/application/HomeRecommendationQueryServiceTest.kt - Create:
src/test/kotlin/kr/co/vividnext/sodalive/v2/recommend/application/CreatorContentViewHistoryServiceTest.kt - Create:
src/test/kotlin/kr/co/vividnext/sodalive/v2/recommend/application/RecommendedCreatorFollowServiceTest.kt - Create:
src/test/kotlin/kr/co/vividnext/sodalive/v2/recommend/application/RecommendationSnapshotRefreshServiceTest.kt - Create:
src/test/kotlin/kr/co/vividnext/sodalive/v2/api/home/HomeRecommendationControllerTest.kt
Phase 1: 도메인 정책과 공통 모델
-
Task 1.1: 추천 점수/신규 부스트 정책 작성
- Files:
- Create:
src/main/kotlin/kr/co/vividnext/sodalive/v2/recommend/domain/RecommendationScorePolicy.kt - Test:
src/test/kotlin/kr/co/vividnext/sodalive/v2/recommend/domain/RecommendationScorePolicyTest.kt
- Create:
- RED:
shouldApplyCreatorNewBoostByDebutDays,shouldApplyAiCharacterNewBoostByCreatedDays,shouldCalculateDebutCreatorScore,shouldCalculateAiChatScore,shouldCalculateCheerScore,shouldCalculateCommunityScore,shouldCalculateFirstAudioRecencyScore테스트를 먼저 작성한다. - 실패 확인:
./gradlew test --tests kr.co.vividnext.sodalive.v2.recommend.domain.RecommendationScorePolicyTest - GREEN: PRD 산식과 부스트 값을 그대로 구현한다. AI 캐릭터 신규 부스트는 캐릭터 생성일 기준 10일 이내 1.5, 20일 이내 1.3, 30일 이내 1.2, 그 외 1을 적용한다. 첫 오디오 최신성 점수는
release_date기준 3일 이내 100, 7일 이내 80, 14일 이내 60, 21일 이내 40, 30일 이내 20을 적용한다. - REFACTOR: 산식별 public 함수명과 파라미터가 PRD 용어를 반영하는지 정리한다.
- 기대 결과: 모든 산식/부스트/최신성 점수 테스트가 PASS이고 소수 계산 오차는
assertEquals(expected, actual, 0.0001)범위 안에 들어간다.
- Files:
-
Task 1.2: 데뷔일/신규 크리에이터 판정 정책 작성
- Files:
- Create:
src/main/kotlin/kr/co/vividnext/sodalive/v2/recommend/domain/CreatorDebutPolicy.kt - Test:
src/test/kotlin/kr/co/vividnext/sodalive/v2/recommend/domain/CreatorDebutPolicyTest.kt
- Create:
- RED: 첫 공개 콘텐츠 일시와 첫 라이브 일시 중 빠른 값을 데뷔일로 선택하는 테스트, 데뷔 후 30일 이내만 true인 테스트를 작성한다.
- 실패 확인:
./gradlew test --tests kr.co.vividnext.sodalive.v2.recommend.domain.CreatorDebutPolicyTest - GREEN:
resolveDebutAt(firstContentPublishedAt, firstLiveAt)와isNewCreator(debutAt, now)를 구현한다. - REFACTOR: 기존
ExplorerService.getCreatorDetail의debutDateTime계산과 비교해 의미가 어긋나지 않는지 확인한다. - 기대 결과: 콘텐츠만 있는 경우, 라이브만 있는 경우, 둘 다 있는 경우, 둘 다 없는 경우가 모두 명확히 검증된다.
- Files:
-
Task 1.3: 섹션/활동 enum과 내부 응답 모델 작성
- Files:
- Create:
src/main/kotlin/kr/co/vividnext/sodalive/v2/recommend/domain/RecommendedActivityType.kt - Create:
src/main/kotlin/kr/co/vividnext/sodalive/v2/recommend/domain/RecommendedSectionType.kt - Modify:
src/main/kotlin/kr/co/vividnext/sodalive/v2/recommend/application/HomeRecommendationQueryService.kt - Test:
src/test/kotlin/kr/co/vividnext/sodalive/v2/recommend/application/HomeRecommendationQueryServiceTest.kt
- Create:
- RED:
LIVE_REPLAY테마 콘텐츠가AUDIO가 아니라LIVE_REPLAY로 분류되는 테스트를 작성한다. - 실패 확인:
./gradlew test --tests kr.co.vividnext.sodalive.v2.recommend.application.HomeRecommendationQueryServiceTest - GREEN: 내부 모델에
LIVE,AUDIO,COMMUNITY,LIVE_REPLAYenum을 추가하고 활동 분류 함수를 구현한다. - REFACTOR: enum 값은 앱 다국어 처리를 위해 영문 code와 동일하게 유지한다.
- 기대 결과: 활동 타입 응답 문자열이 PRD의 enum 후보와 일치한다.
- Files:
Phase 2: 스냅샷 엔티티와 일 1회 집계 작업
-
Task 2.1: 추천 스냅샷 엔티티/리포지토리 추가
- Files:
- Create:
src/main/kotlin/kr/co/vividnext/sodalive/v2/recommend/port/out/RecommendationSnapshotPort.kt - Create:
src/main/kotlin/kr/co/vividnext/sodalive/v2/recommend/adapter/out/persistence/RecommendationSnapshot.kt - Create:
src/main/kotlin/kr/co/vividnext/sodalive/v2/recommend/adapter/out/persistence/RecommendationSnapshotRepository.kt - Test:
src/test/kotlin/kr/co/vividnext/sodalive/v2/recommend/application/RecommendationSnapshotRefreshServiceTest.kt
- Create:
- RED: 섹션 타입, 대상 id, 점수, 기준 시각, 랜덤 tie-breaker를 저장하고 기준 시각별 최신 스냅샷만 읽는 테스트를 작성한다.
- 실패 확인:
./gradlew test --tests kr.co.vividnext.sodalive.v2.recommend.application.RecommendationSnapshotRefreshServiceTest - GREEN:
RecommendationSnapshotJPA 엔티티와findTop...,deleteBySectionTypeAndSnapshotAt계열 리포지토리 메서드를 구현하고, application service가 의존할RecommendationSnapshotPort를 둔다. - REFACTOR: 스냅샷 조회가 없으면 빈 배열을 반환하도록 service 경계에서 처리한다.
- 기대 결과: 스냅샷 없음이 예외가 아니라 빈 결과로 검증된다.
- Files:
-
Task 2.2: 스냅샷 갱신 서비스 작성
- Files:
- Create:
src/main/kotlin/kr/co/vividnext/sodalive/v2/recommend/application/RecommendationSnapshotRefreshService.kt - Create:
src/main/kotlin/kr/co/vividnext/sodalive/v2/recommend/port/out/HomeRecommendationQueryPort.kt - Modify:
src/main/kotlin/kr/co/vividnext/sodalive/v2/recommend/adapter/out/persistence/HomeRecommendationQueryRepository.kt - Modify:
src/main/kotlin/kr/co/vividnext/sodalive/v2/recommend/adapter/out/persistence/DefaultHomeRecommendationQueryRepository.kt - Test:
src/test/kotlin/kr/co/vividnext/sodalive/v2/recommend/application/RecommendationSnapshotRefreshServiceTest.kt
- Create:
- RED: AI 캐릭터, 최근 응원, 인기 커뮤니티 점수를 전날 23:59:59 기준으로 생성하는 테스트를 작성한다.
- 실패 확인:
./gradlew test --tests kr.co.vividnext.sodalive.v2.recommend.application.RecommendationSnapshotRefreshServiceTest - GREEN: 최근 7일 집계와 신규 부스트를 적용해
AI_CHARACTER,CHEER_CREATOR,POPULAR_COMMUNITY스냅샷을 저장한다. AI 캐릭터의followIncrease는 팔로우 대상/관계 정의가 확정되지 않아 이번 스프린트에서 제외하고 0으로 집계한다. - REFACTOR: 무거운 QueryDSL 집계는 repository에 두고 점수 산식은
RecommendationScorePolicy만 사용한다. - 기대 결과: 동일 점수 항목은
randomTieBreaker가 저장되어 조회 시 랜덤 tie-breaker 정렬에 사용할 수 있다.
- Files:
-
Task 2.3: 매일 06:00 KST 스케줄러 추가
- Files:
- Create:
src/main/kotlin/kr/co/vividnext/sodalive/v2/recommend/adapter/out/scheduler/RecommendationSnapshotScheduler.kt - Test:
src/test/kotlin/kr/co/vividnext/sodalive/v2/recommend/application/RecommendationSnapshotRefreshServiceTest.kt
- Create:
- RED: KST 매일 06:00:00 cron과
Asia/Seoulzone이 선언되는지 reflection 테스트를 작성한다. - 실패 확인:
./gradlew test --tests kr.co.vividnext.sodalive.v2.recommend.application.RecommendationSnapshotRefreshServiceTest - GREEN: 스케줄러가 KST 06:00:00 cron으로
RecommendationSnapshotRefreshService.refreshDailySnapshots()를 호출하도록 구현한다. - REFACTOR: 스케줄러에는 집계 로직을 두지 않고 호출만 남긴다.
- 기대 결과: KST 매일 06:00에 전날 23:59:59 KST 기준 집계가 실행되는 계약이 테스트로 고정된다.
- Files:
-
Task 2.4: Phase 2 스냅샷 집계 통합 검증과 경계 보강
- Files:
- Modify:
src/main/kotlin/kr/co/vividnext/sodalive/v2/recommend/application/RecommendationSnapshotRefreshService.kt - Modify:
src/main/kotlin/kr/co/vividnext/sodalive/v2/recommend/adapter/out/scheduler/RecommendationSnapshotScheduler.kt - Modify:
src/main/kotlin/kr/co/vividnext/sodalive/v2/recommend/adapter/out/persistence/DefaultHomeRecommendationQueryRepository.kt - Modify:
src/main/kotlin/kr/co/vividnext/sodalive/v2/recommend/port/out/RecommendationSnapshotPort.kt - Test:
src/test/kotlin/kr/co/vividnext/sodalive/v2/recommend/adapter/out/persistence/DefaultHomeRecommendationQueryRepositoryTest.kt - Test:
src/test/kotlin/kr/co/vividnext/sodalive/v2/recommend/application/RecommendationSnapshotRefreshServiceTest.kt
- Modify:
- RED: QueryDSL 집계 통합 테스트를 추가해 AI 캐릭터 최근 채팅 수/활성 사용자 수, 최근 응원
CHANNEL_DONATION후원 금액/후원 수와 팬 Talk 수, 인기 커뮤니티 좋아요/댓글/팔로워 수가 Phase 2 요구와 일치하는지 검증한다. - 실패 확인:
./gradlew test --tests kr.co.vividnext.sodalive.v2.recommend.adapter.out.persistence.DefaultHomeRecommendationQueryRepositoryTest --tests kr.co.vividnext.sodalive.v2.recommend.application.RecommendationSnapshotRefreshServiceTest - GREEN: 스케줄러 cron을 KST 06:00:00
Asia/Seoulzone으로 수정하고, 최근 응원 후원 금액/후원 수는CanUsage.CHANNEL_DONATION만 집계한다. - REFACTOR:
RecommendationSnapshotPort가 persistence entity를 직접 노출하지 않도록 application/domain 경계 DTO 또는 모델을 도입해port.out의존 경계를 정리한다. - 기대 결과: Phase 2 집계 의미가 DB 기반 테스트로 고정되고, 스케줄러 timezone 계약과
port.out경계 정리가 문서/테스트/구현에 함께 반영된다.
- Files:
-
Task 2.5: 크리에이터 신규 부스트 실제 데뷔일 적용
- Files:
- Modify:
src/main/kotlin/kr/co/vividnext/sodalive/v2/recommend/application/RecommendationSnapshotRefreshService.kt - Modify:
src/main/kotlin/kr/co/vividnext/sodalive/v2/recommend/adapter/out/persistence/DefaultHomeRecommendationQueryRepository.kt - Modify:
src/main/kotlin/kr/co/vividnext/sodalive/v2/recommend/port/out/HomeRecommendationQueryPort.kt - Test:
src/test/kotlin/kr/co/vividnext/sodalive/v2/recommend/adapter/out/persistence/DefaultHomeRecommendationQueryRepositoryTest.kt - Test:
src/test/kotlin/kr/co/vividnext/sodalive/v2/recommend/application/RecommendationSnapshotRefreshServiceTest.kt
- Modify:
- RED: 최근 응원/인기 커뮤니티 신규 부스트가 단순
Member.createdAt이 아니라 실제 데뷔일을 사용하도록 실패 테스트를 추가한다. 실제 데뷔일은 첫 공개 콘텐츠 일시와 첫 라이브 일시 중 빠른 값이며, 둘 다 없는 경우는 스냅샷 후보에서 제외되는 실패 테스트를 추가한다. - 실패 확인:
./gradlew test --tests kr.co.vividnext.sodalive.v2.recommend.adapter.out.persistence.DefaultHomeRecommendationQueryRepositoryTest --tests kr.co.vividnext.sodalive.v2.recommend.application.RecommendationSnapshotRefreshServiceTest - GREEN: 최근 응원/인기 커뮤니티 후보 DTO가 실제 데뷔일을 담도록 QueryDSL 집계를 수정하고, service는 신규 부스트 계산 시 해당 데뷔일만 사용한다.
- REFACTOR: 데뷔일 의미는
CreatorDebutPolicy.resolveDebutAt(...)과 일치하도록 중복 계산을 최소화한다. - 기대 결과: 최근 응원/인기 커뮤니티 신규 부스트가
Member.createdAt이 아니라 실제 데뷔일 기준으로 계산된다.
- Files:
-
Task 2.6: AI 캐릭터 최근 채팅 수를 AI 발화 수로 고정
- Files:
- Modify:
src/main/kotlin/kr/co/vividnext/sodalive/v2/recommend/adapter/out/persistence/DefaultHomeRecommendationQueryRepository.kt - Test:
src/test/kotlin/kr/co/vividnext/sodalive/v2/recommend/adapter/out/persistence/DefaultHomeRecommendationQueryRepositoryTest.kt
- Modify:
- RED: AI 캐릭터 최근 채팅 수가 최근 7일 안에 해당 AI 캐릭터가 발화한 채팅 메시지 수만 세도록 실패 테스트를 추가한다. 사용자 메시지, 다른 캐릭터 메시지, 비활성 메시지, 기간 밖 메시지는 제외되는지 fixture로 검증한다.
- 실패 확인:
./gradlew test --tests kr.co.vividnext.sodalive.v2.recommend.adapter.out.persistence.DefaultHomeRecommendationQueryRepositoryTest - GREEN: QueryDSL where/join 조건을 보강해
recentChatCount가 AI 발화 메시지 수만 반환하도록 구현한다. - REFACTOR: 테스트 이름과 후보 DTO 필드 설명이 PRD의 "AI가 발화한 채팅 수" 의미를 드러내도록 정리한다.
- 기대 결과: AI 캐릭터 추천 점수의
최근 발생한 AI 채팅 수입력값이 AI 발화 수로 고정된다.
- Files:
-
Task 2.7: AI 캐릭터 채팅 활성 사용자 수를 중복 없는 채팅 사용자 수로 고정
- Files:
- Modify:
src/main/kotlin/kr/co/vividnext/sodalive/v2/recommend/adapter/out/persistence/DefaultHomeRecommendationQueryRepository.kt - Test:
src/test/kotlin/kr/co/vividnext/sodalive/v2/recommend/adapter/out/persistence/DefaultHomeRecommendationQueryRepositoryTest.kt
- Modify:
- RED: 최근 활성 사용자 수가 최근 7일 안에 해당 AI 캐릭터와 1회 이상 채팅한 중복 없는 사용자 수로 계산되도록 실패 테스트를 추가한다. 같은 사용자의 다중 메시지는 1명으로 세고, 다른 캐릭터와만 채팅한 사용자는 제외한다.
- 실패 확인:
./gradlew test --tests kr.co.vividnext.sodalive.v2.recommend.adapter.out.persistence.DefaultHomeRecommendationQueryRepositoryTest - GREEN: QueryDSL 집계가 캐릭터별 distinct 사용자 수를 반환하도록 구현한다.
- REFACTOR: 활성 사용자 수 집계는 Task 2.6의 AI 발화 수 집계와 의미가 섞이지 않도록 별도 테스트 케이스로 유지한다.
- 기대 결과: AI 캐릭터 추천 점수의
최근 활성 사용자 수입력값이 중복 없는 채팅 사용자 수로 고정된다.
- Files:
-
Task 2.8: 스냅샷 최종 저장 수를 점수순으로 제한
- Files:
- Modify:
src/main/kotlin/kr/co/vividnext/sodalive/v2/recommend/application/RecommendationSnapshotRefreshService.kt - Modify:
src/main/kotlin/kr/co/vividnext/sodalive/v2/recommend/adapter/out/persistence/DefaultHomeRecommendationQueryRepository.kt - Modify:
src/main/kotlin/kr/co/vividnext/sodalive/v2/recommend/port/out/HomeRecommendationQueryPort.kt - Test:
src/test/kotlin/kr/co/vividnext/sodalive/v2/recommend/application/RecommendationSnapshotRefreshServiceTest.kt - Test:
src/test/kotlin/kr/co/vividnext/sodalive/v2/recommend/adapter/out/persistence/DefaultHomeRecommendationQueryRepositoryTest.kt
- Modify:
- RED: 스냅샷 저장 결과가 최종 점수 내림차순과
randomTieBreaker기준으로 AI 캐릭터 최대 20개, 최근 응원이 많은 크리에이터 최대 16개, 인기 커뮤니티 최대 20개만 저장되는 실패 테스트를 추가한다. - 실패 확인:
./gradlew test --tests kr.co.vividnext.sodalive.v2.recommend.application.RecommendationSnapshotRefreshServiceTest --tests kr.co.vividnext.sodalive.v2.recommend.adapter.out.persistence.DefaultHomeRecommendationQueryRepositoryTest - GREEN: repository 조회에서 최종 점수와
randomTieBreaker를 계산하고, 점수 정렬 이후 동점자 랜덤 노출 여지를 위한 섹션별 최종 저장 수를 적용한다. service는 기준 시각 계산과 snapshot replace만 담당한다. - REFACTOR:
GENRE_CREATOR는 Phase 2 스냅샷 갱신 대상이 아니라 Task 4.2의 조회 이력 기반 추천임을 문서/테스트 경계로 유지한다. - 기대 결과: application/service가 전체 후보를 메모리로 불러와 점수를 계산하지 않고, DB에서 정확한 최종 top 후보를 동점자 랜덤 정렬까지 반영해 반환하고 저장한다.
- Files:
-
Task 2.9: DB-side exact scoring으로 스냅샷 후보 산정 전환
- Files:
- Create:
src/main/kotlin/kr/co/vividnext/sodalive/v2/recommend/domain/RecommendationScoreSpec.kt - Modify:
src/main/kotlin/kr/co/vividnext/sodalive/v2/recommend/domain/RecommendationScorePolicy.kt - Modify:
src/main/kotlin/kr/co/vividnext/sodalive/v2/recommend/application/RecommendationSnapshotRefreshService.kt - Modify:
src/main/kotlin/kr/co/vividnext/sodalive/v2/recommend/adapter/out/persistence/DefaultHomeRecommendationQueryRepository.kt - Modify:
src/main/kotlin/kr/co/vividnext/sodalive/v2/recommend/port/out/HomeRecommendationQueryPort.kt - Test:
src/test/kotlin/kr/co/vividnext/sodalive/v2/recommend/domain/RecommendationScorePolicyTest.kt - Test:
src/test/kotlin/kr/co/vividnext/sodalive/v2/recommend/adapter/out/persistence/DefaultHomeRecommendationQueryRepositoryTest.kt - Test:
src/test/kotlin/kr/co/vividnext/sodalive/v2/recommend/application/RecommendationSnapshotRefreshServiceTest.kt
- Create:
- RED:
RecommendationScoreSpec공유 산식과 DB-scored snapshot 조회 계약이 없으면 컴파일/테스트가 실패하도록 테스트를 작성한다. native SQL을 사용하는 쿼리는 KotlinRecommendationScorePolicy기대값과 DB score를 비교하고, 부스트 경계일, null aggregate, 비활성/제외 row,score desc, randomTieBreaker asc정렬, 최종 점수 계산 이후 limit 적용, H2 MySQL mode parameter binding 호환성을 함께 검증한다. - 실패 확인:
./gradlew test --tests kr.co.vividnext.sodalive.v2.recommend.domain.RecommendationScorePolicyTest --tests kr.co.vividnext.sodalive.v2.recommend.adapter.out.persistence.DefaultHomeRecommendationQueryRepositoryTest --tests kr.co.vividnext.sodalive.v2.recommend.application.RecommendationSnapshotRefreshServiceTest - GREEN: DB 조회에서 모든 적격 후보의 최종 score와
randomTieBreaker를 계산한 뒤score desc, randomTieBreaker asc정렬과 섹션별 최종 limit을 적용한다. service는 기준 시각 계산과 snapshot replace만 담당하고 Kotlin-side score 재계산과 service-side limit을 제거한다. - REFACTOR: DB score expression과 Kotlin
RecommendationScorePolicy가RecommendationScoreSpec의 가중치/부스트 구간 상수를 공유하도록 정리하고, 최근 응원/인기 커뮤니티 집계는 aggregate CTE 기반으로 중복 계산을 줄인다. - 기대 결과: candidate pre-limit 없이 DB에서 정확한 최종 top 후보를 산정하고, 20/16/20 저장 상한은 최종 점수 계산과 동점 랜덤 정렬 이후 적용되는 저장 limit으로만 유지된다.
- Files:
Phase 3: 추천 조회 repository와 application service
-
Task 3.1: 라이브/배너/활동 크리에이터 조회 구현
- Files:
- Modify:
src/main/kotlin/kr/co/vividnext/sodalive/v2/recommend/port/out/HomeRecommendationQueryPort.kt - Modify:
src/main/kotlin/kr/co/vividnext/sodalive/v2/recommend/adapter/out/persistence/HomeRecommendationQueryRepository.kt - Modify:
src/main/kotlin/kr/co/vividnext/sodalive/v2/recommend/adapter/out/persistence/DefaultHomeRecommendationQueryRepository.kt - Modify:
src/main/kotlin/kr/co/vividnext/sodalive/v2/recommend/application/HomeRecommendationQueryService.kt - Test:
src/test/kotlin/kr/co/vividnext/sodalive/v2/recommend/application/HomeRecommendationQueryServiceTest.kt - Test:
src/test/kotlin/kr/co/vividnext/sodalive/v2/recommend/adapter/out/persistence/DefaultHomeRecommendationQueryRepositoryTest.kt
- Modify:
- RED: 라이브 최신순 20개, 활성 배너 orders 정렬 최대 20개, 동일
orders배너의 랜덤 tie-breaker 정렬, 크리에이터당 최신 활동 1개만 반환하는 테스트를 작성한다. 라이브 노출 정보는 크리에이터 닉네임/프로필 이미지/라이브 번호를 포함하고, 활동 크리에이터 노출 정보는 크리에이터 프로필 이미지/닉네임/활동 타입/UTC 활동 시간/이동 대상 id를 포함하며 라이브 활동의 이동 대상 id는 nullable임을 검증한다. 배너는 비활성 이벤트 대상EVENT, 비활성 크리에이터 대상CREATOR, 비활성 시리즈 대상SERIES, 비활성 시리즈 소유 회원 대상SERIES가 제외되고,LINK배너는 별도 대상 엔티티 검증 없이 배너 자체 활성 상태만으로 노출되는 repository 테스트를 함께 작성한다. - 실패 확인:
./gradlew test --tests kr.co.vividnext.sodalive.v2.recommend.application.HomeRecommendationQueryServiceTest --tests kr.co.vividnext.sodalive.v2.recommend.adapter.out.persistence.DefaultHomeRecommendationQueryRepositoryTest - GREEN:
LiveRoom,AudioContentBanner,AudioContent,CreatorCommunity기반 QueryDSL 조회를 구현한다. application service는HomeRecommendationQueryPort에만 의존하고 persistence 구현체가 port를 구현한다. 배너는 기존 콘텐츠 홈 배너의 앱 이동 필드를 유지하고, 동일orders값은 후보군 축소 후 랜덤화하거나 랜덤 tie-breaker를 적용한다. 배너 대상 활성 조건은 service 후처리가 아니라 repository 조회 조건으로 고정한다. 활동 타입 enum 값은LIVE,AUDIO,COMMUNITY,LIVE_REPLAY영문 code 그대로 유지한다. - REFACTOR: 차단 관계, 비활성 회원, 비활성 콘텐츠/배너 제외 조건을 공통 private 조건 함수로 정리한다. 단순 조회와 대상 활성 조건은 QueryDSL/JPA 우선으로 표현하고, native SQL은 SQL 고급 기능이 필요한 쿼리에만 남긴다.
- 기대 결과: 특정 섹션 데이터가 부족해도 service가 가능한 개수만 반환한다.
- Files:
-
Task 3.2: 최근 데뷔/첫 오디오 콘텐츠 조회 구현
- Files:
- Modify:
src/main/kotlin/kr/co/vividnext/sodalive/v2/recommend/adapter/out/persistence/HomeRecommendationQueryRepository.kt - Modify:
src/main/kotlin/kr/co/vividnext/sodalive/v2/recommend/adapter/out/persistence/DefaultHomeRecommendationQueryRepository.kt - Modify:
src/main/kotlin/kr/co/vividnext/sodalive/v2/recommend/application/HomeRecommendationQueryService.kt - Test:
src/test/kotlin/kr/co/vividnext/sodalive/v2/recommend/application/HomeRecommendationQueryServiceTest.kt
- Modify:
- RED: 데뷔 후 30일 이내 추천 점수순, 최근 데뷔 크리에이터 노출 정보의 프로필 이미지/닉네임, 첫 오디오 콘텐츠 3번째 이내 활성 콘텐츠만 인정, 최신성 점수 구간별 정렬, 예약 공개 콘텐츠 제외 테스트를 작성한다.
- 실패 확인:
./gradlew test --tests kr.co.vividnext.sodalive.v2.recommend.application.HomeRecommendationQueryServiceTest - GREEN: 데뷔일 계산, 최근 7일/30일 집계,
release_date기준 최신성 점수, 동점 랜덤 정렬을 구현한다. - REFACTOR: 데뷔일 계산은
CreatorDebutPolicy, 산식은RecommendationScorePolicy만 호출하도록 중복 제거한다. - 기대 결과: 앞선 비활성 콘텐츠가 3개 이상이면 이후 활성 콘텐츠가 제외된다.
- Files:
-
Task 3.3: AI 캐릭터/응원/인기 커뮤니티 스냅샷 조회 구현
- Files:
- Modify:
src/main/kotlin/kr/co/vividnext/sodalive/v2/recommend/application/HomeRecommendationQueryService.kt - Modify:
src/main/kotlin/kr/co/vividnext/sodalive/v2/recommend/adapter/out/persistence/DefaultHomeRecommendationQueryRepository.kt - Test:
src/test/kotlin/kr/co/vividnext/sodalive/v2/recommend/application/HomeRecommendationQueryServiceTest.kt
- Modify:
- RED: 스냅샷 기준 AI 캐릭터 10개, AI 캐릭터 응답의 캐릭터 이름/소개/전체 채팅 수/오리지널 작품명 조건, 최근 응원 8명과 크리에이터 프로필 이미지/닉네임, 인기 커뮤니티 10개와 크리에이터 프로필 이미지/닉네임/UTC 시간/좋아요 수/댓글 수/커뮤니티 내용, 스냅샷 없음 빈 배열 테스트를 작성한다.
- 실패 확인:
./gradlew test --tests kr.co.vividnext.sodalive.v2.recommend.application.HomeRecommendationQueryServiceTest - GREEN:
RecommendationSnapshotRepository에서 최신 스냅샷을 읽고 대상 엔티티 상세 정보를 조립한다. AI 캐릭터 작품명은 오리지널 작품 캐릭터인 경우에만 채우고, 인기 커뮤니티는 스냅샷에 저장된 점수/랜덤 tie-breaker 순서를 유지한다. - REFACTOR: 비활성/노출 제한 캐릭터, 커뮤니티 비공개/유료/핀/성인 조건을 repository 조건으로 고정한다.
- 기대 결과: AI 캐릭터 노출 필드가 PRD와 일치하고, 인기 커뮤니티는 크리에이터당 1개만 반환하며 동일 점수는 스냅샷 생성 시 저장한 랜덤 tie-breaker 기준으로 노출된다.
- Files:
Phase 4: 콘텐츠 조회 이력 기록
-
Task 4.1: 콘텐츠 조회 이력 엔티티/서비스 작성
- Files:
- Create:
src/main/kotlin/kr/co/vividnext/sodalive/v2/recommend/port/out/CreatorContentViewHistoryPort.kt - Create:
src/main/kotlin/kr/co/vividnext/sodalive/v2/recommend/adapter/out/persistence/CreatorContentViewHistory.kt - Create:
src/main/kotlin/kr/co/vividnext/sodalive/v2/recommend/adapter/out/persistence/CreatorContentViewHistoryRepository.kt - Create:
src/main/kotlin/kr/co/vividnext/sodalive/v2/recommend/application/CreatorContentViewHistoryService.kt - Test:
src/test/kotlin/kr/co/vividnext/sodalive/v2/recommend/application/CreatorContentViewHistoryServiceTest.kt
- Create:
- RED: 인증 회원의 콘텐츠 상세 진입 시 memberId/contentId/genreId/viewedAt이 저장되는 테스트와 비회원은 저장하지 않는 테스트를 작성한다.
- 실패 확인:
./gradlew test --tests kr.co.vividnext.sodalive.v2.recommend.application.CreatorContentViewHistoryServiceTest - GREEN: 이력 저장 service와 repository를 구현한다. application service는
CreatorContentViewHistoryPort에만 의존하고 persistence 구현체가 port를 구현한다. - REFACTOR: 동일 회원/콘텐츠의 연속 중복 저장 허용 여부는 추천 이력으로 보존하며, 집계 시 distinct 장르 기준으로 처리한다.
- 기대 결과: 조회 이력 저장 실패가 콘텐츠 상세 조회 자체를 실패시키지 않도록 호출부에서 예외 전파 범위를 제한한다.
- Files:
-
Task 4.2: 장르 기반 크리에이터 추천 조회 구현
- 선행 조건: Task 4.1의
CreatorContentViewHistory엔티티/리포지토리/저장 service가 준비되어 있어야 한다. - Files:
- Modify:
src/main/kotlin/kr/co/vividnext/sodalive/v2/recommend/application/HomeRecommendationQueryService.kt - Modify:
src/main/kotlin/kr/co/vividnext/sodalive/v2/recommend/adapter/out/persistence/DefaultHomeRecommendationQueryRepository.kt - Test:
src/test/kotlin/kr/co/vividnext/sodalive/v2/recommend/application/HomeRecommendationQueryServiceTest.kt
- Modify:
- RED: 조회 이력 콘텐츠의
content_theme기준 랜덤 5개, 부족분 랜덤 보충, 테마별 8명, 한 응답의 5개 테마 안에서 크리에이터 중복 제거, 서로 다른 조회 시점에서는 같은 크리에이터 재노출 허용, 팔로우 크리에이터 제외, 활성 크리에이터/활성 콘텐츠가 없어 빈 그룹이 되는 테마 제외, 크리에이터 프로필 이미지/닉네임/id 노출 테스트를 작성한다. - 실패 확인:
./gradlew test --tests kr.co.vividnext.sodalive.v2.recommend.application.HomeRecommendationQueryServiceTest - GREEN:
CreatorContentViewHistory.contentId와content.theme_id매핑을 기반으로 후보 테마/크리에이터를 조회한다. 기존 응답 필드명은 공개 스키마 호환을 위해genreId,genreName을 유지하되 값은content_theme.id,content_theme.theme을 담는다. - REFACTOR: 성인 콘텐츠 테마는
MemberContentPreference.isAdultContentVisible == true회원에게만 포함되도록 조건을 공통화한다. - 기대 결과: 비회원 또는 조회 이력 없는 회원도 조회 가능한 테마 중 랜덤 5개를 받고, 활성 크리에이터/활성 콘텐츠가 없는 빈 그룹은 제외한 뒤 다른 테마로 보충된다.
- 선행 조건: Task 4.1의
-
Task 4.3: 기존 콘텐츠 상세 조회 흐름에 이력 기록 연결
- Files:
- Modify:
src/main/kotlin/kr/co/vividnext/sodalive/content/AudioContentService.kt - Test:
src/test/kotlin/kr/co/vividnext/sodalive/content/AudioContentServiceTest.kt
- Modify:
- RED:
getDetail성공 시CreatorContentViewHistoryService.recordView(...)가 호출되는 테스트를 작성한다. - 실패 확인:
./gradlew test --tests kr.co.vividnext.sodalive.content.AudioContentServiceTest - GREEN:
AudioContentService생성자에 optional하지 않은 신규 service 의존성을 추가하고 상세 조회 성공 지점에서 기록한다. - REFACTOR: 기존
GetAudioContentDetailResponse스키마와 Controller URL/응답은 변경하지 않는다. - 기대 결과: 기존 상세 조회 테스트가 모두 통과하고 응답 JSON 필드가 바뀌지 않는다.
- Files:
Phase 5: 추천 크리에이터 동시 팔로우
-
Task 5.1: 팔로우 use case 작성
- Files:
- Create:
src/main/kotlin/kr/co/vividnext/sodalive/v2/recommend/application/RecommendedCreatorFollowService.kt - Test:
src/test/kotlin/kr/co/vividnext/sodalive/v2/recommend/application/RecommendedCreatorFollowServiceTest.kt
- Create:
- RED: 신규 팔로우 id와 이미 팔로우/본인 id 등 제외 id를 구분하는 테스트, 존재하지 않는 id/크리에이터가 아닌 id 포함 시 전체 실패 테스트를 작성한다.
- 실패 확인:
./gradlew test --tests kr.co.vividnext.sodalive.v2.recommend.application.RecommendedCreatorFollowServiceTest - GREEN:
MemberRepository,CreatorFollowingRepository를 사용해 전체 입력을 검증하고, 이미 팔로우 중인 id와 본인 id는skippedCreatorIds로 구분하며 신규 팔로우만 저장한다. - REFACTOR: 섹션별 분기 없이 팔로우 처리 로직은
followCreators(member, creatorIds)하나로 유지한다. - 기대 결과: 존재하지 않는 id 또는 크리에이터가 아닌 id가 하나라도 있으면 신규 저장이 발생하지 않고, 이미 팔로우 중인 id와 본인 id는 실패가 아니라 제외 id로 반환된다.
- Files:
-
Task 5.2: 팔로우 API DTO/Controller 연결
- Files:
- Create:
src/main/kotlin/kr/co/vividnext/sodalive/v2/api/home/dto/FollowRecommendedCreatorsRequest.kt - Create:
src/main/kotlin/kr/co/vividnext/sodalive/v2/api/home/dto/FollowRecommendedCreatorsResponse.kt - Modify:
src/main/kotlin/kr/co/vividnext/sodalive/v2/api/home/adapter/in/web/HomeRecommendationController.kt - Test:
src/test/kotlin/kr/co/vividnext/sodalive/v2/api/home/HomeRecommendationControllerTest.kt
- Create:
- RED: 비로그인 요청은
common.error.bad_credentials, 로그인 요청은creatorIds를 service에 전달하고 결과를ApiResponse.ok로 반환하는 controller 테스트를 작성한다. - 실패 확인:
./gradlew test --tests kr.co.vividnext.sodalive.v2.api.home.HomeRecommendationControllerTest - GREEN:
POST /api/v2/home/recommendations/creators/follow를 구현한다. - REFACTOR: request id 리스트가 비어 있으면
SodaException으로 거부한다. - 기대 결과: 응답에
followedCreatorIds, 이미 팔로우 중인 id와 본인 id를 포함한skippedCreatorIds가 포함된다.
- Files:
Phase 6: 홈 통합/전체보기 API
-
Task 6.1: 홈 통합 응답 DTO와 facade 작성
- Files:
- Create:
src/main/kotlin/kr/co/vividnext/sodalive/v2/api/home/dto/HomeRecommendationResponse.kt - Create:
src/main/kotlin/kr/co/vividnext/sodalive/v2/api/home/application/HomeRecommendationFacade.kt - Test:
src/test/kotlin/kr/co/vividnext/sodalive/v2/api/home/HomeRecommendationControllerTest.kt
- Create:
- RED: 통합 조회가 섹션별 기본 limit(20/20/10/10/10/10/5x8/8/10)을 service에 전달하고, 인증 회원의 팔로우 제외/콘텐츠 조회 이력/본인인증 여부를 service 조건으로 전달하는 테스트를 작성한다.
- 실패 확인:
./gradlew test --tests kr.co.vividnext.sodalive.v2.api.home.HomeRecommendationControllerTest - GREEN: facade가
HomeRecommendationQueryService결과를 API DTO로 변환한다. 인증 회원이면 팔로우 제외, 콘텐츠 조회 이력, 본인인증 여부 조건을 조회 context에 포함하고 비회원이면 회원 의존 조건을 제외한다. - REFACTOR: API DTO에는 앱 이동 대상 id가 없는 라이브 활동의 target id를 nullable로 둔다.
- 기대 결과: 특정 섹션이 빈 배열이어도 통합 조회는 성공 응답이다.
- Files:
-
Task 6.2: 홈 통합 Controller 작성
- Files:
- Create:
src/main/kotlin/kr/co/vividnext/sodalive/v2/api/home/adapter/in/web/HomeRecommendationController.kt - Test:
src/test/kotlin/kr/co/vividnext/sodalive/v2/api/home/HomeRecommendationControllerTest.kt
- Create:
- RED:
GET /api/v2/home/recommendations가 인증 회원/비회원 모두 호출 가능하고ApiResponse.ok를 반환하는 테스트를 작성한다. - 실패 확인:
./gradlew test --tests kr.co.vividnext.sodalive.v2.api.home.HomeRecommendationControllerTest - GREEN:
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?패턴으로 구현한다. - REFACTOR: controller에는 인증 null 허용과 request parameter 전달 외 로직을 두지 않는다.
- 기대 결과: 비회원은 회원 의존 조건 없이 기본 추천을 받는다.
- Files:
-
Task 6.3: 섹션별 전체보기 API 작성
- Files:
- Create:
src/main/kotlin/kr/co/vividnext/sodalive/v2/api/home/dto/HomeRecommendationPageResponse.kt - Modify:
src/main/kotlin/kr/co/vividnext/sodalive/v2/api/home/adapter/in/web/HomeRecommendationController.kt - Modify:
src/main/kotlin/kr/co/vividnext/sodalive/v2/api/home/application/HomeRecommendationFacade.kt - Test:
src/test/kotlin/kr/co/vividnext/sodalive/v2/api/home/HomeRecommendationControllerTest.kt
- Create:
- RED: 라이브/최근 데뷔/첫 오디오/AI 캐릭터/인기 커뮤니티 전체보기 endpoint가
page,size를 전달하는 테스트를 작성한다. - 실패 확인:
./gradlew test --tests kr.co.vividnext.sodalive.v2.api.home.HomeRecommendationControllerTest - GREEN: 확정 URL 5개를 controller에 추가하고
HomeRecommendationPageResponse로 반환한다. - REFACTOR: size 기본값은 홈 기본 노출 수와 분리해
20으로 두고 최대값은50으로 제한한다. - 기대 결과: 모든 전체보기 API가 같은 페이징 응답 형식을 사용한다.
- Files:
Phase 7: 통합 검증과 문서 갱신
-
Task 7.1: repository 조건 회귀 테스트 보강
- Files:
- Test:
src/test/kotlin/kr/co/vividnext/sodalive/v2/recommend/application/HomeRecommendationQueryServiceTest.kt
- Test:
- RED: 차단 관계 양방향 제외, 커뮤니티 성인 노출 조건, 본인인증 여부 조건이 필요한 추천 필터, 팔로우 크리에이터 제외, 비활성 회원 제외 테스트를 추가한다.
- 실패 확인:
./gradlew test --tests kr.co.vividnext.sodalive.v2.recommend.application.HomeRecommendationQueryServiceTest - GREEN: 누락 조건을 QueryDSL where 조건 또는 service 필터에 추가한다.
- REFACTOR: 같은 차단/성인 조건이 여러 쿼리에 반복되면 repository private 함수로만 정리한다.
- 기대 결과: PRD Edge Case와 회원 조건이 테스트 이름으로 추적된다.
- Files:
-
Task 7.2: 운영 지표 기록 지점 확인
- Files:
- Modify:
src/main/kotlin/kr/co/vividnext/sodalive/v2/api/home/application/HomeRecommendationFacade.kt - Modify:
src/main/kotlin/kr/co/vividnext/sodalive/v2/recommend/application/CreatorContentViewHistoryService.kt - Modify:
src/main/kotlin/kr/co/vividnext/sodalive/v2/recommend/application/RecommendedCreatorFollowService.kt - Modify:
src/main/kotlin/kr/co/vividnext/sodalive/v2/recommend/application/RecommendationSnapshotRefreshService.kt - Test:
src/test/kotlin/kr/co/vividnext/sodalive/v2/api/home/HomeRecommendationControllerTest.kt - Test:
src/test/kotlin/kr/co/vividnext/sodalive/v2/recommend/application/CreatorContentViewHistoryServiceTest.kt - Test:
src/test/kotlin/kr/co/vividnext/sodalive/v2/recommend/application/RecommendedCreatorFollowServiceTest.kt - Test:
src/test/kotlin/kr/co/vividnext/sodalive/v2/recommend/application/RecommendationSnapshotRefreshServiceTest.kt
- Modify:
- RED: 메인 홈 API 성공/실패와 응답 시간, 섹션별 빈 응답 여부, 전체보기 조회 수, 추천 섹션별 클릭률 기록 지점, 동시 팔로우 요청/성공 수, 콘텐츠 조회 이력 기록 성공/실패, 콘텐츠 상세 조회 흐름에서
CreatorContentViewHistoryService.recordView(...)실패가runCatching으로 삼켜지더라도 구조화 로그 또는 metric으로 관측되는지, 일 배치 집계 성공/실패와 소요 시간을 관측할 수 있는 로그 또는 metric 호출 테스트를 작성한다. - 실패 확인:
./gradlew test --tests kr.co.vividnext.sodalive.v2.api.home.HomeRecommendationControllerTest --tests kr.co.vividnext.sodalive.v2.recommend.application.CreatorContentViewHistoryServiceTest --tests kr.co.vividnext.sodalive.v2.recommend.application.RecommendedCreatorFollowServiceTest --tests kr.co.vividnext.sodalive.v2.recommend.application.RecommendationSnapshotRefreshServiceTest - GREEN: 프로젝트에 이미 사용하는 metric 클라이언트가 있으면 해당 클라이언트를 사용하고, 없으면 구조화 로그로 PRD Metrics 항목을 관측 가능하게 남긴다. 콘텐츠 조회 이력 저장 실패는 상세 조회 응답 실패로 전파하지 않되, 실패 원인과
memberId,contentId를 추적 가능한 형태로 남긴다. - REFACTOR: 지표 기록 때문에 공개 응답 스키마나 비즈니스 분기가 바뀌지 않도록 application 경계에서만 정리한다.
- 기대 결과: PRD Metrics 항목이 구현 코드의 로그/metric 지점으로 추적되고 테스트에서 호출 여부를 확인할 수 있다. 특히 콘텐츠 상세 조회의 이력 저장 실패가 사용자 응답을 깨지 않으면서도 운영자가 감지 가능한 신호로 남는다.
- Files:
-
Task 7.3: 전체 테스트/린트 검증
- Files:
- Modify:
docs/20260529_메인_홈_추천_API/plan-task.md
- Modify:
- TDD 예외 사유: 검증 명령 실행과 문서 기록 task라 제품 코드 테스트를 새로 작성하지 않는다.
- 대체 검증 방법:
./gradlew test./gradlew ktlintCheck./gradlew tasks --all
- 기대 결과: 세 명령이 모두 성공하고, 이 문서 하단 검증 기록에 실행 일시/명령/결과를 누적한다.
- Files:
-
Task 7.4: 신규 엔티티 테이블 생성 SQL 문서화
- Files:
- Create:
docs/20260529_메인_홈_추천_API/create-new-entity-tables.sql - Modify:
docs/20260529_메인_홈_추천_API/plan-task.md
- Create:
- TDD 예외 사유: 운영 DB 반영용 SQL 문서 산출물 작성 task라 제품 코드 테스트를 새로 작성하지 않는다.
- 대체 검증 방법:
rg -n "CREATE TABLE|recommendation_snapshot|creator_content_view_history" docs/20260529_메인_홈_추천_API/create-new-entity-tables.sql./gradlew tasks --all
- 작성 기준: Phase 7까지 완료된 최종 JPA 엔티티 필드/인덱스/nullable 조건을 기준으로
RecommendationSnapshot,CreatorContentViewHistory등 이번 작업에서 신규 생성된 엔티티의 운영 DB 테이블 생성 SQL을 작성한다. - REFACTOR: SQL 문서에는 이번 작업에서 새로 추가된 테이블만 포함하고, 기존 테이블 변경이나 데이터 마이그레이션은 별도 배포 절차 항목으로 분리한다.
- 기대 결과: Phase 7 완료 시점의 최종 엔티티 구조와 일치하는 신규 테이블 생성 SQL이 문서로 남아 운영 DB 반영 범위를 검토할 수 있다.
- Files:
PRD Coverage Check
- Feature A: Phase 3, Phase 6, Phase 7에서 통합 조회, limit, 인증/비회원, 팔로우 제외, 콘텐츠 조회 이력, 본인인증 여부, 차단 필터, 스냅샷 빈 배열 처리를 검증한다.
- Feature B: Task 3.1, Task 6.3에서 라이브 최신순/전체보기/비활성 회원 제외와 크리에이터 닉네임/프로필 이미지/라이브 번호 노출 필드를 검증한다.
- Feature C: Task 3.1에서 기존 콘텐츠 홈 배너 재활용, orders 정렬, 동일 orders 랜덤 정렬, 활성 배너/콘텐츠 조건,
EVENT/CREATOR/SERIES대상 비활성 제외,LINK배너의 자체 활성 상태 기준 노출, 앱 이동 필드 유지를 검증한다. - Feature D: Task 1.3, Task 3.1에서 활동 타입 영문 enum, 최신 활동 1개, 크리에이터 프로필 이미지/닉네임, UTC 시간, 이동 대상 id nullable을 검증한다.
- Feature E: Task 1.1, Task 1.2, Task 3.2, Task 6.3에서 데뷔일/점수/동점 랜덤 정렬/프로필 이미지와 닉네임 노출/전체보기를 검증한다.
- Feature F: Task 1.1, Task 3.2, Task 6.3에서 첫 오디오 콘텐츠 판정, 최신성 점수 구간, 예약 공개 제외를 검증한다.
- Feature G: Task 1.1, Task 2.2, Task 2.6, Task 2.7, Task 2.8, Task 2.9, Task 3.3, Task 6.3에서 AI 캐릭터 점수, 캐릭터 생성일 기준 신규 부스트, 스냅샷, AI 채팅 집계 범위, DB-side exact scoring, 응답 필드, 오리지널 작품명 조건, 전체보기를 검증한다.
- Feature H: Task 4.1, Task 4.2, Task 4.3에서 장르 조회 이력, 조회 이력 없을 때 랜덤 장르, 부족분 랜덤 보충, 한 응답 내 크리에이터 중복 제거, 조회 시점별 재노출 허용, 팔로우 제외, 성인 장르 조건, 크리에이터 프로필 이미지/닉네임/id 노출을 검증한다.
- Feature I: Task 5.1, Task 5.2에서 장르의 크리에이터와 최근 응원이 많은 크리에이터가 공통 동시 팔로우 use case를 재사용하고, 이미 팔로우 중인 id와 본인 id는
skippedCreatorIds로 구분하며, 존재하지 않는 id/크리에이터가 아닌 id는 전체 실패로 처리하는지 검증한다. - Feature J: Task 1.1, Task 2.2, Task 2.4, Task 2.5, Task 2.8, Task 2.9, Task 3.3, Task 5.1, Task 5.2에서 최근 응원 점수/스냅샷 조회, 8명 limit, 크리에이터 프로필 이미지/닉네임 노출,
CHANNEL_DONATION기준 후원 금액/후원 수, 팬 Talk 수, 최근 7일 집계, 데뷔일 기준 신규 부스트, DB-side exact scoring, 해당 섹션의 동시 팔로우를 검증한다. - Feature K: Task 1.1, Task 2.2, Task 2.5, Task 2.8, Task 2.9, Task 3.3, Task 6.3, Task 7.1에서 인기 커뮤니티 점수/조건/노출 필드(크리에이터 프로필 이미지, 닉네임, UTC 시간, 좋아요 수, 댓글 수, 내용)/댓글 불가 게시글 댓글 수 0점 계산, 데뷔일 기준 신규 부스트, 최근 7일 집계, DB-side exact scoring, 전체보기를 검증한다.
- Metrics: Task 7.2에서 메인 홈 API 성공률/응답 시간, 섹션별 빈 응답 비율, 전체보기 API 조회 수, 추천 섹션별 클릭률, 동시 팔로우 요청/성공 수, 콘텐츠 조회 이력 기록 성공률, 일 배치 집계 성공/실패 수와 스냅샷 생성 소요 시간의 로그 또는 metric 기록 지점을 검증한다.
- Technical Constraints/Non-Goals: Phase 1~7에서
v2.api.home/v2.recommend패키지 경계,port.out의존 방향, 신규 v2 endpoint 분리, 기존 공개 스키마 유지, 서버 다국어 번역/ML 개인화/A-B 테스트/관리자 화면/수동 편집 제외 조건을 검증한다. 응답 enum 영문 code 안정성은 Task 1.3과 Task 3.1에서,RecommendationSnapshotPort의 persistence entity 노출 정리는 Task 2.4에서, 점수 기반 스냅샷의RecommendationScoreSpec공유 산식과 candidate pre-limit 금지는 Task 2.9에서, JPA/QueryDSL 우선 및 native SQL 제한 사용 전략은 Task 2.9와 Task 3.1에서, 신규 엔티티 테이블 생성 SQL 문서화는 Task 7.4에서 검증한다.
Verification Log
- 2026-05-30: plan-task 문서 작성 전
docs/agent-guides/작업절차.md,docs/agent-guides/문서유지보수.md,docs/agent-guides/코드스타일.md,docs/agent-guides/테스트스타일.md,docs/agent-guides/실행명령어.md,docs/20260529_메인_홈_추천_API/prd.md를 확인했다. - 2026-05-30: 기존 v2 패키지 구조, 테스트 스타일, QueryDSL/스케줄러 사용 패턴, 관련 엔티티/리포지토리 후보를
find,rg,sed로 확인해 계획의 파일 경로와 검증 명령에 반영했다. - 2026-05-30:
./gradlew tasks --all을 실행했다. sandbox 기본 권한에서는/Users/klaus/.gradle/.../gradle-8.1.1-bin.zip.lck생성 권한 문제로 실패했고, 권한 승인 후 재실행해BUILD SUCCESSFUL in 13s를 확인했다. - 2026-05-30: 사용자 피드백에 따라 PRD의 Feature I가 특정 섹션 한정이 아니라 공통 "여러 크리에이터 동시 팔로우" 요구사항임을 확인했다. 장르의 크리에이터와 최근 응원이 많은 크리에이터가 동일한 팔로우 로직을 쓰도록 endpoint를
POST /api/v2/home/recommendations/creators/follow로 일반화했다. - 2026-05-30: 동시 팔로우 범위 수정 후
rg로 장르 전용 명칭(GenreCreator,genre-creators,FollowGenre)과 placeholder 문구가 남지 않았음을 확인했다../gradlew tasks --all은 sandbox 기본 권한에서 동일한.gradlelock 파일 권한 문제로 실패했고, 권한 승인 후 재실행해BUILD SUCCESSFUL in 752ms를 확인했다. - 2026-05-30:
sourceSection은 PRD 필수 요구사항이 아니므로 제거했다. 동시 팔로우 요청은creatorIds만 받도록 단순화하고, 장르의 크리에이터/최근 응원이 많은 크리에이터 화면은 같은 API를 호출하는 것으로 정리했다. - 2026-05-30:
sourceSection제거 후./gradlew tasks --all을 실행했다. sandbox 기본 권한에서는 동일한.gradlelock 파일 권한 문제로 실패했고, 권한 승인 후 재실행해BUILD SUCCESSFUL in 718ms를 확인했다. - 2026-05-30: PRD와 plan-task를 대조해 본인인증 조건, 동일 orders 배너 랜덤 정렬, AI 캐릭터 응답 필드/캐릭터 생성일 기준 부스트, 첫 오디오 최신성 점수 구간, 댓글 불가 커뮤니티 점수 계산, Metrics 관측 지점,
port.out의존 경계 보강이 필요함을 확인하고 관련 task와 Coverage Check에 반영했다. - 2026-05-30: 문서 보강 후
./gradlew tasks --all을 실행했다. sandbox 기본 권한에서는 동일한.gradlelock 파일 권한 문제로 실패했고, 권한 승인 후 재실행해BUILD SUCCESSFUL in 789ms를 확인했다. - 2026-05-30: Phase 1 Task 1.1 RED/GREEN을 진행했다.
RecommendationScorePolicyTest는 구현 전Unresolved reference: RecommendationScorePolicy로 실패했고, 구현 후./gradlew test --tests kr.co.vividnext.sodalive.v2.recommend.domain.RecommendationScorePolicyTest가BUILD SUCCESSFUL로 통과했다. - 2026-05-30: Phase 1 Task 1.2 RED/GREEN을 진행했다.
CreatorDebutPolicyTest는 구현 전Unresolved reference: CreatorDebutPolicy로 실패했고, 구현 후./gradlew test --tests kr.co.vividnext.sodalive.v2.recommend.domain.CreatorDebutPolicyTest가BUILD SUCCESSFUL로 통과했다. - 2026-05-30: Phase 1 Task 1.3 RED/GREEN을 진행했다.
HomeRecommendationQueryServiceTest는 구현 전RecommendedActivityType,RecommendedSectionType,HomeRecommendationQueryService미구현으로 실패했고, 구현 후./gradlew test --tests kr.co.vividnext.sodalive.v2.recommend.application.HomeRecommendationQueryServiceTest가BUILD SUCCESSFUL로 통과했다. - 2026-05-30: Phase 1 최종 검증으로
./gradlew test --tests 'kr.co.vividnext.sodalive.v2.recommend.*',./gradlew ktlintCheck,./gradlew test를 실행했고 모두BUILD SUCCESSFUL로 통과했다. Kotlin LSP는 이 환경에kotlin-lsp가 설치되어 있지 않아 실행하지 못했고, Gradle 컴파일/테스트/ktlint로 대체 확인했다. - 2026-05-30: Phase 2 Task 2.1~2.3 RED/GREEN을 진행했다.
RecommendationSnapshotRefreshServiceTest는 구현 전RecommendationSnapshot,RecommendationSnapshotPort,HomeRecommendationQueryPort,RecommendationSnapshotRefreshService,RecommendationSnapshotScheduler미구현으로 실패했고, 구현 후./gradlew test --tests kr.co.vividnext.sodalive.v2.recommend.application.RecommendationSnapshotRefreshServiceTest가BUILD SUCCESSFUL로 통과했다. - 2026-05-30: Phase 2 검증으로
./gradlew clean test --tests kr.co.vividnext.sodalive.v2.recommend.application.RecommendationSnapshotRefreshServiceTest,./gradlew test --tests 'kr.co.vividnext.sodalive.v2.recommend.*',./gradlew ktlintCheck,./gradlew test를 순차 실행했고 모두BUILD SUCCESSFUL로 통과했다. 병렬 Gradle 실행 중에는 KAPT 임시 stub 파일 경합이 발생해 이후 검증은 순차 실행으로 고정했다. Kotlin LSP는 이 환경에kotlin-lsp가 설치되어 있지 않아 실행하지 못했고, Gradle 컴파일/테스트/ktlint로 대체 확인했다. - 2026-05-30: 기본 구현체 명명 규칙을 접미사
Impl대신 접두사Default로 변경했다.HomeRecommendationQueryRepositoryImpl은DefaultHomeRecommendationQueryRepository로 바꿨고, PRD와 구현 계획에 AI 캐릭터followIncrease는 팔로우 대상/관계 정의 확정 전까지 이번 스프린트 산식과 집계에서 제외한다고 기록했다. - 2026-05-30: 구현 전 문서 보강으로 기본 구현체 명명 규칙을
docs/agent-guides/코드스타일.md에 반영하고, 당시 스냅샷 일 배치 기준을 PRD/Task 2.3~2.4에 기록했다. 이후 Phase 2 권고 보강에서 스케줄은 KST 06:00Asia/Seoulzone으로 변경했다. QueryDSL 집계 통합 테스트,RecommendationSnapshotPort경계 정리, 최근 응원CHANNEL_DONATION기준 후원 금액/후원 수 검증은 Task 2.4로 추가했다. - 2026-05-30: Phase 2 Task 2.4 RED/GREEN을 진행했다. RED에서
RecommendationSnapshotRefreshServiceTest는 기존 KST cron/JVM timezone 기준 계산으로 실패했고,DefaultHomeRecommendationQueryRepositoryTest는 최근 응원 후원 금액이CHANNEL_DONATION외 사용처까지 포함되어expected: <150> but was: <3150>으로 실패했다. GREEN 후./gradlew test --tests kr.co.vividnext.sodalive.v2.recommend.application.RecommendationSnapshotRefreshServiceTest --tests kr.co.vividnext.sodalive.v2.recommend.adapter.out.persistence.DefaultHomeRecommendationQueryRepositoryTest,./gradlew ktlintCheck,./gradlew test --tests 'kr.co.vividnext.sodalive.v2.recommend.*'를 순차 실행했고 모두BUILD SUCCESSFUL로 통과했다. - 2026-05-30: Phase 2 재점검을 진행했다.
RecommendationSnapshotRefreshServiceTest와DefaultHomeRecommendationQueryRepositoryTest는 각각 재실행 시BUILD SUCCESSFUL로 통과했지만, 최근 응원/인기 커뮤니티 신규 부스트가 실제 데뷔일이 아니라Member.createdAt에 의존하는 점, AI 캐릭터 최근 채팅 수의 participant 범위가 명확히 고정되지 않은 점, 스냅샷 후보 전체 저장은 과도한 데이터 저장으로 이어질 수 있다는 점을 확인했다. 해당 보완사항은 Task 2.5~2.8과 Coverage Check에 나누어 반영했고, 실제 데뷔일이 없는 크리에이터는 Task 2.5에서 스냅샷 후보 제외로 확정하고 테스트로 검증했다. - 2026-05-30: Phase 2 Task 2.5~2.8 보강을 진행했다.
DefaultHomeRecommendationQueryRepositoryTest와RecommendationSnapshotRefreshServiceTest에 실제 데뷔일 기준 후보, AI 발화 수, 중복 없는 활성 사용자 수, 섹션별 스냅샷 저장 상한(20/16/20) 검증을 추가했다. 첫 실행은AudioContent.themefixture 누락과 QueryDSL alias 문제로 실패했고, 보정 후./gradlew test --tests kr.co.vividnext.sodalive.v2.recommend.adapter.out.persistence.DefaultHomeRecommendationQueryRepositoryTest --tests kr.co.vividnext.sodalive.v2.recommend.application.RecommendationSnapshotRefreshServiceTest가BUILD SUCCESSFUL로 통과했다. - 2026-05-30: Phase 2 Task 2.5~2.8 최종 검증으로
./gradlew test --tests 'kr.co.vividnext.sodalive.v2.recommend.*',./gradlew ktlintCheck,./gradlew test를 순차 실행했고 모두BUILD SUCCESSFUL로 통과했다. Kotlin LSP는 이 환경에kotlin-lsp가 설치되어 있지 않아 실행하지 못했고, Gradle 컴파일/테스트/ktlint로 대체 확인했다. - 2026-05-30: Phase 2 권고 보강으로 스냅샷 스케줄을 KST 06:00
Asia/Seoulzone으로 변경했다. 최종 점수 계산 전 후보 사전 제한은 정확한 top 후보를 누락할 수 있어 적용하지 않는다. AI 20개, 최근 응원 16개, 인기 커뮤니티 20개 저장 상한은 최종 점수와 동점 랜덤 정렬 이후 repository에서 적용하는 최종 limit으로 유지한다. - 2026-05-30: 사용자 피드백에 따라 service가 전체 후보를 모두 불러와 점수를 계산하는 구조를 DB-side exact scoring으로 전환하기로 확정했다. PRD와 Task 2.9에
RecommendationScoreSpec공유 산식, DB 최종 점수 계산 후 정렬/limit, candidate pre-limit 금지, service scoring 제거 요구사항을 반영했다. 기존 20/16/20 저장 상한은 동점자 랜덤 노출 여지를 위한 최종 저장 limit으로 유지하되, 최종 점수 계산 전 후보 제한 의미로는 사용하지 않도록 명확히 했다. - 2026-05-31: Phase 2 Task 2.9 RED/GREEN을 진행했다. RED에서
RecommendationScoreSpec과 DB-scored snapshot 조회 계약 미구현으로RecommendationScorePolicyTest,DefaultHomeRecommendationQueryRepositoryTest,RecommendationSnapshotRefreshServiceTest컴파일이 실패했다. GREEN에서RecommendationScoreSpec을 추가하고, AI/최근 응원/인기 커뮤니티 스냅샷 조회가 DB에서 최종 score와randomTieBreaker를 계산한 뒤score desc, randomTieBreaker asc정렬과 최종 limit을 적용하도록 변경했다.RecommendationSnapshotRefreshService에서는 Kotlin-side score 재계산과 service-side limit을 제거했다. - 2026-05-31: Phase 2 Task 2.9 검증으로
./gradlew test --tests kr.co.vividnext.sodalive.v2.recommend.domain.RecommendationScorePolicyTest --tests kr.co.vividnext.sodalive.v2.recommend.adapter.out.persistence.DefaultHomeRecommendationQueryRepositoryTest --tests kr.co.vividnext.sodalive.v2.recommend.application.RecommendationSnapshotRefreshServiceTest,./gradlew test --tests 'kr.co.vividnext.sodalive.v2.recommend.*',./gradlew ktlintCheck,./gradlew test를 순차 실행했고 모두BUILD SUCCESSFUL로 통과했다. - 2026-05-31: Phase 2 Task 2.9 리뷰 후속으로
RecommendationScoreSpec에 신규 부스트 일수 상수를 추가하고,RecommendationScorePolicy와 native SQL boost window가 같은 상수를 쓰도록 정리했다. 최근 응원/인기 커뮤니티 native SQL은 후보 행마다 donation/comment/follower/debut 집계를 반복하지 않도록 aggregate CTE 기반으로 변경했고, 데뷔일은 콘텐츠 공개일과 라이브 시작일을union all한 이벤트 집계에서min(debut_at)으로 계산해 DB-side exact scoring 의미를 유지했다. PRD/plan-task의 동일 점수 정렬 문구는 스냅샷 저장randomTieBreaker기준으로 맞췄다. - 2026-05-31: 리뷰 후속 검증으로
./gradlew test --tests kr.co.vividnext.sodalive.v2.recommend.domain.RecommendationScorePolicyTest --tests kr.co.vividnext.sodalive.v2.recommend.adapter.out.persistence.DefaultHomeRecommendationQueryRepositoryTest --tests kr.co.vividnext.sodalive.v2.recommend.application.RecommendationSnapshotRefreshServiceTest,./gradlew test --tests 'kr.co.vividnext.sodalive.v2.recommend.*',./gradlew ktlintCheck,./gradlew test를 순차 실행했고 모두BUILD SUCCESSFUL로 통과했다. 중간에 H2 native SQL 타입 추론 문제와 ktlint line length 오류가 있었고, 데뷔일 CTE를union all이벤트 집계로 단순화하고 긴setParameter호출을 줄바꿈해 해결했다. - 2026-05-31: Phase 3 Task 3.1 RED/GREEN을 진행했다. RED에서
HomeRecommendationQueryServiceTest와DefaultHomeRecommendationQueryRepositoryTest는 라이브/배너/최근 활동 크리에이터 조회 record, port 메서드, service 메서드, repository 쿼리 미구현으로 컴파일 실패했다. GREEN에서 라이브 최신순 20개, 활성 배너 orders 정렬/동일 orders 랜덤 tie-breaker/최대 20개, 크리에이터당 최신 활동 1개와LIVE/AUDIO/COMMUNITY/LIVE_REPLAY분류를 구현했고,./gradlew test --tests kr.co.vividnext.sodalive.v2.recommend.application.HomeRecommendationQueryServiceTest --tests kr.co.vividnext.sodalive.v2.recommend.adapter.out.persistence.DefaultHomeRecommendationQueryRepositoryTest가BUILD SUCCESSFUL로 통과했다. - 2026-05-31: Phase 3 Task 3.1 추가 검증으로
./gradlew ktlintCheck와./gradlew test --tests 'kr.co.vividnext.sodalive.v2.recommend.*'를 실행했고 모두BUILD SUCCESSFUL로 통과했다. 중간에 테스트 코드 line length ktlint 오류가 있었고, 긴 fixture 호출을 줄바꿈해 해결했다. - 2026-05-31: Phase 3 Task 3.2 RED/GREEN을 진행했다. RED에서
HomeRecommendationQueryServiceTest와DefaultHomeRecommendationQueryRepositoryTest는 최근 데뷔 크리에이터/첫 오디오 콘텐츠 record, port 메서드, service 메서드, repository 쿼리 미구현으로 컴파일 실패했다. GREEN에서 실제 데뷔일 30일 이내 최근 데뷔 크리에이터 점수/랜덤 tie-breaker 정렬, 첫 3개 업로드 이내 활성 공개 오디오 콘텐츠 판정, 비활성 선행 콘텐츠 경계, 예약 공개 제외,release_date최신성 점수 정렬을 구현했고,./gradlew test --tests kr.co.vividnext.sodalive.v2.recommend.application.HomeRecommendationQueryServiceTest --tests kr.co.vividnext.sodalive.v2.recommend.adapter.out.persistence.DefaultHomeRecommendationQueryRepositoryTest가BUILD SUCCESSFUL로 통과했다. - 2026-05-31: Phase 3 Task 3.3 RED/GREEN을 진행했다. RED에서
HomeRecommendationQueryServiceTest와DefaultHomeRecommendationQueryRepositoryTest는 AI 캐릭터/최근 응원/인기 커뮤니티 상세 record, port 메서드, service 메서드, repository 상세 조회 미구현으로compileTestKotlin이 실패했다. GREEN에서RecommendationSnapshotPort.findLatestSnapshots(sectionType)기반 최신 스냅샷 조회, AI 캐릭터 10개/최근 응원 8명/인기 커뮤니티 10개 limit, 스냅샷 순서 보존, 누락 상세 필터링, 인기 커뮤니티 크리에이터 중복 제거, 커뮤니티 비노출 조건 필터링, AI 캐릭터 원작명 조건부 응답과 전체 AI 발화 수 조립을 구현했다../gradlew test --rerun-tasks --tests kr.co.vividnext.sodalive.v2.recommend.application.HomeRecommendationQueryServiceTest --tests kr.co.vividnext.sodalive.v2.recommend.adapter.out.persistence.DefaultHomeRecommendationQueryRepositoryTest --tests kr.co.vividnext.sodalive.v2.recommend.application.RecommendationSnapshotRefreshServiceTest와./gradlew ktlintCheck가BUILD SUCCESSFUL로 통과했다. - 2026-05-31: Phase 3에는
CreatorContentViewHistory엔티티/리포지토리/저장 서비스가 아직 구현되어 있지 않아 장르 기반 크리에이터 추천 조회를 포함하지 않았다. 해당 산출물은 Phase 4 Task 4.1 범위이므로, 장르 기반 크리에이터 추천 조회는 조회 이력 저장 모델이 준비된 뒤 Phase 4 Task 4.2에서 별도 RED/GREEN으로 진행한다. - 2026-05-31: Phase 3 리뷰 후속으로 인기 커뮤니티 추천이 상위 10개 스냅샷을 먼저 자른 뒤 크리에이터 중복을 제거해 10개 미만으로 내려갈 수 있는 문제를 보완했다. RED에서 상위 스냅샷 중복 제거 후 뒤 후보로 기본 10개를 채우는 테스트가 실패했고, GREEN에서 인기 커뮤니티만 최신 스냅샷 후보 20개를 읽은 뒤 상세 조립/크리에이터 중복 제거 후 최종 10개를 반환하도록 수정했다. 후속 검증으로
./gradlew test --rerun-tasks --tests kr.co.vividnext.sodalive.v2.recommend.application.HomeRecommendationQueryServiceTest --tests kr.co.vividnext.sodalive.v2.recommend.adapter.out.persistence.DefaultHomeRecommendationQueryRepositoryTest --tests kr.co.vividnext.sodalive.v2.recommend.application.RecommendationSnapshotRefreshServiceTest,./gradlew test --tests 'kr.co.vividnext.sodalive.v2.recommend.*',./gradlew ktlintCheck,./gradlew test를 순차 실행했고 모두BUILD SUCCESSFUL로 통과했다. - 2026-05-31: 사용자 피드백에 따라 신규 엔티티 테이블 생성 SQL은 Phase 7 완료 후 최종 엔티티 구조 기준으로 작성하도록 계획을 보강했다.
RecommendationSnapshot,CreatorContentViewHistory등 이번 작업에서 새로 생성된 엔티티의 운영 DB DDL을docs/20260529_메인_홈_추천_API/create-new-entity-tables.sql산출물로 남기는 Task 7.4를 추가했다. - 2026-05-31: PRD와 plan-task를 재대조해 큰 기능 흐름은 반영되어 있었으나 일부 PRD 세부 항목의 task 추적성이 약한 점을 확인했다. 라이브/활동/최근 데뷔/최근 응원/인기 커뮤니티/장르 크리에이터 노출 필드,
LINK배너 자체 활성 상태 기준, 활동 enum 영문 code 안정성, 한 응답 내 장르 크리에이터 중복 제거와 조회 시점별 재노출 허용, 추천 섹션별 클릭률 metric, Non-Goals 범위를 관련 task와 Coverage Check에 보강했다. - 2026-05-31: Phase 2/3 재점검 후속으로 배너 대상 활성 조건과 스냅샷 데뷔일 계산의 빈
channel_name라이브 제외 누락을 확인했다. RED에서DefaultHomeRecommendationQueryRepositoryTest에 회귀 테스트를 추가했고shouldExcludeHomeBannersWithInactiveTargetsExceptLink,shouldExcludeBlankChannelNameLiveFromSnapshotDebutAt가 실패했다. GREEN에서findHomeBanners에EVENT/CREATOR/SERIES대상 활성 조건을 추가하고, 최근 응원/인기 커뮤니티 데뷔일 CTE에lr.channel_name <> ''조건을 추가했다. 리뷰 중 PRD의 인기 커뮤니티 성인 노출 조건은 항상 제외가 아니라MemberContentPreference.isAdultContentVisible == true회원에게 노출 허용임을 재확인해, 스냅샷 산정은 성인 게시글도 후보로 유지하고 상세 조회에서includeAdultCommunities로 필터링하도록 수정했다. 추가 코드 리뷰에서 기존 홈 배너가tabId = 1일 때tab is null만 조회하는 계약을 확인해,findHomeBanners에cb.tab_id is null조건과 탭 전용 배너 제외 회귀 테스트를 보강했다. 후속 검증으로./gradlew test --tests kr.co.vividnext.sodalive.v2.recommend.adapter.out.persistence.DefaultHomeRecommendationQueryRepositoryTest,./gradlew test --tests kr.co.vividnext.sodalive.v2.recommend.application.HomeRecommendationQueryServiceTest --tests kr.co.vividnext.sodalive.v2.recommend.adapter.out.persistence.DefaultHomeRecommendationQueryRepositoryTest --tests kr.co.vividnext.sodalive.v2.recommend.application.RecommendationSnapshotRefreshServiceTest,./gradlew test --tests 'kr.co.vividnext.sodalive.v2.recommend.*',./gradlew ktlintCheck가 모두BUILD SUCCESSFUL로 통과했다. 병렬 Gradle test 실행 중 XML 결과 파일 쓰기 충돌로 한 번 실패했으나, 동일 명령 단독 재실행 시 성공해 테스트 assertion 실패가 아님을 확인했다. - 2026-05-31: 사용자 피드백에 따라 여러 크리에이터 동시 팔로우에서 본인 크리에이터 id는 전체 실패 조건에서 제외하고, 이미 팔로우 중인 id와 동일하게
skippedCreatorIds로 반환하도록 PRD와 plan-task를 수정했다. - 2026-05-31: Phase 4 구현 중
./gradlew test --tests kr.co.vividnext.sodalive.v2.recommend.application.CreatorContentViewHistoryServiceTest,./gradlew test --tests kr.co.vividnext.sodalive.v2.recommend.application.HomeRecommendationQueryServiceTest,./gradlew test --tests kr.co.vividnext.sodalive.v2.recommend.adapter.out.persistence.DefaultHomeRecommendationQueryRepositoryTest,./gradlew test --tests kr.co.vividnext.sodalive.content.AudioContentServiceTest를 실행해 모두BUILD SUCCESSFUL을 확인했다.