Files
sodalive-backend-spring-boot/docs/20260529_메인_홈_추천_API/prd.md

21 KiB

PRD: 메인 홈 추천 API

1. Overview

메인 홈에서 여러 추천 섹션을 한 번에 조회하고, 각 섹션의 전체보기/페이징 조회와 일부 사용자 액션을 지원하는 v2 API를 제공한다.


2. Problem

  • 기존 홈/콘텐츠/라이브/AI 캐릭터/커뮤니티/후원 도메인의 데이터가 여러 화면과 API에 흩어져 있어 신규 메인 홈 구성을 한 API 계약으로 제공하기 어렵다.
  • 추천 섹션별 정렬 기준, 점수 산식, 갱신 주기, 노출 필드가 서로 달라 구현 전 명확한 요구사항 문서가 필요하다.
  • 일부 섹션은 일 단위 집계 스냅샷이 필요하고, 일부 섹션은 실시간성 또는 랜덤성이 필요하므로 조회 API와 집계 작업의 책임을 분리해야 한다.
  • kr.co.vividnext.sodalive.v2 외부 코드는 엔티티만 재활용해야 하므로 신규 API/서비스/조회 로직의 패키지 경계를 사전에 확정해야 한다.

3. Goals

  • 메인 홈 추천 섹션을 v2 패키지 하위 신규 코드로 제공한다.
  • 홈 첫 화면에서 필요한 섹션별 기본 개수를 조회할 수 있다.
  • 전체보기 요구가 있는 섹션은 별도 리스트 API로 페이징 조회할 수 있다.
  • 홈 배너는 기존 콘텐츠 홈 배너 데이터를 재활용한다.
  • 점수 기반 섹션은 요구된 산식, 기간, 신규 부스트를 반영한다.
  • 일 1회 갱신 섹션은 KST 매일 06:00에 전날 23:59:59 KST 기준 데이터로 계산된 결과를 사용한다. 서버 스케줄러 cron은 Asia/Seoul zone의 KST 06:00으로 등록한다.
  • 시간 응답은 UTC 기준으로 내려주고 앱에서 표시 포맷과 다국어를 처리한다.
  • 장르 기반 크리에이터 추천을 위해 콘텐츠 조회 이력 기록 방식을 도입한다.
  • 여러 크리에이터를 동시에 팔로우하는 API를 제공한다.

4. Non-Goals

  • 기존 kr.co.vividnext.sodalive.v2 외부의 Controller, Service, Repository 구현 코드를 직접 재사용하지 않는다.
  • 기존 홈 API, 콘텐츠 홈 API, 라이브 API, AI 캐릭터 API의 공개 스키마를 변경하지 않는다.
  • 앱 다국어 문구를 서버에서 번역해 내려주지 않는다.
  • 추천 산식의 머신러닝 모델화, 개인화 가중치 학습, A/B 테스트 플랫폼은 이번 범위에 포함하지 않는다.
  • 관리자 화면 신규 개발은 포함하지 않는다.
  • 추천 결과 수동 편집 기능은 포함하지 않는다.

5. Target Users

  • 회원: 메인 홈에서 라이브, 신규 크리에이터, 콘텐츠, AI 캐릭터, 커뮤니티, 후원 기반 추천을 탐색하는 사용자
  • 비회원: 인증 없이 조회 가능한 추천 섹션을 탐색하는 사용자
  • 앱 클라이언트: 섹션별 노출 정보와 이동 대상 id를 받아 홈 UI와 전체보기 화면을 구성하는 클라이언트

6. User Stories

  • 사용자는 메인 홈 진입 시 라이브 중인 방송 20개를 최신순으로 보고 싶다.
  • 사용자는 홈 배너를 최대 20개까지 정해진 노출 순서대로 보고 싶다.
  • 사용자는 방금 활동한 크리에이터와 활동 영역을 확인하고 해당 콘텐츠/커뮤니티로 이동하고 싶다.
  • 사용자는 최근 데뷔한 크리에이터를 추천 점수순으로 보고 전체 리스트도 확인하고 싶다.
  • 사용자는 신규 크리에이터가 올린 첫 번째 오디오 콘텐츠를 발견하고 전체보기로 더 탐색하고 싶다.
  • 사용자는 AI 캐릭터를 추천 점수순으로 보고 채팅 화면으로 이동하고 싶다.
  • 사용자는 내가 봤던 콘텐츠 장르 또는 랜덤 장르 기준으로 팔로우하지 않은 크리에이터를 추천받고 싶다.
  • 사용자는 장르 추천에서 여러 크리에이터를 한 번에 팔로우하고 싶다.
  • 사용자는 최근 응원이 많은 크리에이터를 순위로 보고 싶다.
  • 사용자는 인기 커뮤니티 게시글을 크리에이터별로 중복 없이 보고 싶다.

7. Core Features

Feature A. 메인 홈 통합 조회

Requirements

  • 신규 홈 API는 kr.co.vividnext.sodalive.v2 하위 패키지에 작성한다.
  • 메인 홈 통합 API URL prefix는 /api/v2/home/recommendations를 사용한다.
  • 홈 첫 화면 응답은 섹션별 기본 limit만 포함한다.
  • 섹션별 기본 노출 수는 다음과 같다.
    • 라이브 중인 방송: 20개
    • 홈 배너: 최대 20개
    • 방금 활동한 크리에이터: 10개
    • 최근 데뷔한 크리에이터: 10개
    • 처음부터 함께 성장: 10개
    • 크리에이터와 이야기를 나눠요: 10개
    • 장르의 크리에이터: 장르 최대 5개, 장르별 크리에이터 8명
    • 최근 응원이 많은 크리에이터: 8명
    • 인기 커뮤니티: 10개
  • 인증 회원이면 팔로우 제외, 콘텐츠 조회 이력, 본인인증 여부 등 사용자 조건을 반영한다.
  • 인증 회원이 차단했거나 인증 회원을 차단한 크리에이터의 라이브, 콘텐츠, 커뮤니티, 크리에이터 추천 데이터는 노출하지 않는다.
  • 비회원이면 회원 의존 조건을 제외한 기본 추천만 제공한다.

Edge Cases

  • 섹션별 데이터가 부족하면 부족한 개수만 내려주고 전체 API는 성공 처리한다.
  • 특정 섹션 집계 스냅샷이 없으면 해당 섹션은 빈 배열로 내려주고 장애가 전체 홈 조회를 막지 않도록 한다.
  • 앱 이동에 필요한 id가 없는 섹션은 이동 대상 필드를 nullable로 둔다.

Feature B. 라이브 중인 방송

Requirements

  • 라이브 중인 방송을 최신순으로 조회한다.
  • 홈 첫 화면은 20개를 내려준다.
  • 전체 리스트 API는 페이징으로 조회할 수 있어야 한다.
  • 노출 정보는 크리에이터 닉네임, 프로필 이미지, 라이브 번호를 포함한다.
  • 기존 LiveRoom, Member 등 엔티티는 재활용할 수 있다.

Edge Cases

  • 방송자가 비활성 회원이면 노출하지 않는다.

Feature C. 홈 배너

Requirements

  • 기존 콘텐츠 홈 배너를 재활용한다.
  • orders 기준으로 최대 20개를 조회한다.
  • 활성 배너만 노출한다.
  • 동일 orders 값이 있으면 랜덤으로 정렬한다.
  • 배너 대상 콘텐츠가 비활성 처리되었으면 노출하지 않는다.
  • 기존 배너 응답에서 앱 이동에 필요한 필드는 유지한다.

Feature D. 방금 활동한 크리에이터

Requirements

  • 최신순 10개를 조회한다.
  • 활동 타입은 enum으로 내려주며 앱에서 다국어 처리한다.
  • 활동 타입 후보는 LIVE, AUDIO, COMMUNITY, LIVE_REPLAY로 한다.
  • 오디오는 콘텐츠를 업로드한 경우를 의미한다.
  • 커뮤니티는 커뮤니티 게시글을 등록한 경우를 의미한다.
  • 라이브는 라이브 진행 후 종료한 경우를 의미한다.
  • 라이브 다시듣기는 콘텐츠 업로드 시 다시듣기 테마로 올린 경우를 의미한다.
  • 노출 정보는 크리에이터 프로필 이미지, 닉네임, 활동 타입, UTC 기반 활동 시간, 이동 대상 id를 포함한다.
  • 라이브 활동은 별도 이동 대상 id가 필요하지 않다.
  • 라이브 외 활동은 콘텐츠 id 또는 커뮤니티 게시글 id를 내려준다.
  • 크리에이터당 최신 활동 1개만 노출한다.

Edge Cases

  • 다시듣기 콘텐츠는 AUDIO가 아니라 LIVE_REPLAY로 분류한다.

Feature E. 최근 데뷔한 크리에이터

Requirements

  • 홈 첫 화면은 추천순 10개를 조회한다.
  • 전체 리스트 API는 페이징으로 조회할 수 있어야 한다.
  • 데뷔일은 콘텐츠를 처음 공개한 날과 라이브를 한 날 중 빠른 날짜로 계산한다.
  • 데뷔일 계산 로직은 기존 ExplorerService.getCreatorDetaildebutDateTime 계산 방식과 동일하게 맞춘다.
  • 데뷔 후 30일 이내 크리에이터만 대상으로 한다.
  • 추천 점수는 ((팔로우 증가량 * 0.35) + (콘텐츠 활동 점수 * 0.3) + (소통 점수 * 0.2)) * 신규 부스트로 계산한다.
  • 팔로우 증가량은 최근 7일간 신규 팔로우한 유저 수로 계산한다.
  • 콘텐츠 활동 점수는 최근 30일간 업로드 콘텐츠 수와 라이브 횟수로 계산한다.
  • 소통 점수는 최근 7일간 커뮤니티 게시글 수, 커뮤니티 게시글 댓글 수, 커뮤니티 게시글 좋아요 수, 콘텐츠 댓글 수, 콘텐츠 좋아요 수로 계산한다.
  • 신규 부스트는 데뷔 후 10일 이내 1.5, 20일 이내 1.3, 30일 이내 1.2를 적용한다.
  • 추천 점수가 동일하면 랜덤으로 정렬한다.
  • 노출 정보는 크리에이터 프로필 이미지, 닉네임을 포함한다.

Feature F. 처음부터 함께 성장

Requirements

  • 신규 크리에이터가 올린 첫 번째 오디오 콘텐츠를 조회한다.
  • 신규 크리에이터는 데뷔일로부터 30일 이내인 크리에이터다.
  • 홈 첫 화면은 최대 10개를 조회한다.
  • 전체보기 API는 신규 크리에이터의 첫 번째 콘텐츠를 페이징 조회할 수 있어야 한다.
  • 첫 번째 콘텐츠 판정은 해당 크리에이터의 오디오 콘텐츠를 created_at, release_date 기준으로 정렬해 3번째 이내에 업로드된 활성 콘텐츠인 경우로 한다.
  • 앞선 비활성 콘텐츠가 2개 있고 3번째 콘텐츠가 활성이라면 첫 번째 콘텐츠로 인정한다.
  • 앞선 비활성 콘텐츠가 3개 이상이면 이후 활성 콘텐츠는 첫 번째 콘텐츠로 인정하지 않는다.
  • 최신성 점수 기준일은 release_date로 본다.
  • 최신성 점수는 3일 이내 100, 7일 이내 80, 14일 이내 60, 21일 이내 40, 30일 이내 20으로 계산한다.
  • 정렬은 최신성 점수 내림차순, 동점이면 랜덤으로 한다.

Edge Cases

  • 예약 공개 콘텐츠는 공개 전에는 노출하지 않는다.

Feature G. 크리에이터와 이야기를 나눠요

Requirements

  • AI 캐릭터 리스트를 조회한다.
  • 홈 첫 화면은 10개를 조회한다.
  • 전체 리스트 API는 페이징으로 조회할 수 있어야 한다.
  • 노출 정보는 캐릭터 이름, 캐릭터 소개, 작품명, 사용자들이 친 전체 채팅 수를 포함한다.
  • 작품명은 오리지널 작품 캐릭터인 경우에만 내려준다.
  • 1차 정렬은 AI 채팅 추천 점수 내림차순이다.
  • 2차 정렬은 동일 점수인 경우 랜덤이다.
  • AI 채팅 추천 점수는 이번 스프린트에서 ((0.45 * 최근 발생한 AI 채팅 수) + (0.35 * 최근 활성 사용자 수)) * 신규 부스트로 계산한다.
  • 최근 발생한 AI 채팅 수와 최근 활성 사용자 수는 최근 7일 데이터 기반으로 계산한다.
  • 최근 발생한 AI 채팅 수는 AI 캐릭터가 발화한 채팅 메시지 수를 의미한다.
  • 최근 활성 사용자 수는 최근 7일 안에 해당 AI 캐릭터와 1회 이상 채팅한 중복 없는 사용자 수를 의미한다.
  • AI 캐릭터의 팔로우 증가량은 팔로우 대상/관계의 정확한 정의가 확정되지 않아 이번 스프린트 산식과 집계에서 제외한다. 추후 AI 캐릭터 팔로우 정의가 확정되면 별도 요구사항으로 재도입한다.
  • 신규 부스트는 캐릭터 생성일 기준 10일 이내 1.5, 20일 이내 1.3, 30일 이내 1.2, 그 외 1을 적용한다.
  • 점수는 KST 매일 06:00에 전날 23:59:59 KST 기준 데이터로 1회 갱신한다. 스케줄러는 Asia/Seoul zone의 KST 06:00 기준으로 실행한다.

Edge Cases

  • 비활성 또는 노출 제한 캐릭터는 제외한다.

Feature H. 장르의 크리에이터

Requirements

  • 사용자가 조회한 장르가 없으면 조회 가능한 장르 중 랜덤 5개를 선별한다.
  • 사용자가 조회한 콘텐츠가 있으면 조회한 콘텐츠들의 장르 중 랜덤 5개를 선별한다.
  • 조회 이력 기반 장르가 5개 미만이면 나머지 조회 가능한 장르 중 랜덤으로 채운다.
  • 각 장르별로 해당 장르의 콘텐츠를 업로드한 크리에이터를 랜덤 8명씩 노출한다.
  • 같은 크리에이터가 서로 다른 조회 시점의 여러 장르 섹션에 노출될 수는 있다.
  • 한 번에 조회되는 5개 장르 안에서는 같은 크리에이터가 중복 노출되지 않아야 한다.
  • 사용자가 팔로우한 크리에이터는 제외한다.
  • 성인 콘텐츠 장르는 MemberContentPreference.isAdultContentVisible == true인 회원에게만 노출한다.
  • 노출 정보는 크리에이터 프로필 이미지, 닉네임, id를 포함한다.
  • 콘텐츠 조회 데이터는 콘텐츠 상세 진입 시점에 기록한다.

Edge Cases

  • 장르별 추천 가능한 크리에이터가 8명 미만이면 가능한 만큼만 내려준다.

Feature I. 여러 크리에이터 동시 팔로우

Requirements

  • 크리에이터 id 리스트를 받아 해당 id의 크리에이터 중 팔로우되어 있지 않은 크리에이터를 모두 팔로우한다.
  • 이미 팔로우한 크리에이터는 성공 처리에서 제외하거나 중복 없이 유지한다.
  • 응답은 실제 신규 팔로우된 크리에이터 id 목록과 이미 팔로우 중이었거나 처리 제외된 id 목록을 구분할 수 있어야 한다.
  • 요청 id 중 일부라도 존재하지 않는 id, 크리에이터가 아닌 회원 id, 본인 id 등 유효하지 않은 값이 포함되면 전체 실패로 처리한다.

Edge Cases

  • 이미 팔로우 중인 크리에이터 id는 유효한 id로 보며 실패 사유로 처리하지 않는다.

Feature J. 최근 응원이 많은 크리에이터

Requirements

  • 응원 점수가 높은 크리에이터 8명을 조회한다.
  • 노출 정보는 크리에이터 프로필 이미지, 크리에이터 닉네임을 포함한다.
  • 응원 점수는 ((0.6 * 후원 금액) + (0.3 * 팬 Talk 수) + (0.1 * 후원 수)) * 신규 부스트로 계산한다.
  • 후원 금액과 후원 수는 CanUsage.CHANNEL_DONATION 데이터만 대상으로 계산한다.
  • 팬톡은 기존 CreatorCheers를 의미한다.
  • 점수는 최근 7일 데이터를 기반으로 계산한다.
  • 신규 부스트는 데뷔 후 10일 이내 1.5, 20일 이내 1.3, 30일 이내 1.2, 그 외 1을 적용한다.
  • 신규 부스트의 데뷔일은 Member.createdAt이 아니라 콘텐츠를 처음 공개한 날과 라이브를 한 날 중 빠른 날짜로 계산한다.
  • 점수는 KST 매일 06:00에 전날 23:59:59 KST 기준 데이터로 1회 갱신한다. 스케줄러는 Asia/Seoul zone의 KST 06:00 기준으로 실행한다.

Feature K. 인기 커뮤니티

Requirements

  • 홈 첫 화면은 10개를 조회한다.
  • 전체 리스트 API는 페이징으로 조회할 수 있어야 한다.
  • 노출 정보는 크리에이터 프로필 이미지, 닉네임, UTC 시간, 좋아요 수, 댓글 수, 커뮤니티 내용을 포함한다.
  • 크리에이터당 1개의 커뮤니티 게시글만 노출한다.
  • 비공개 커뮤니티 게시글은 제외한다.
  • 유료 커뮤니티 게시글은 제외한다.
  • 핀으로 고정한 커뮤니티 게시글은 제외한다.
  • 성인 속성을 가진 커뮤니티 게시글은 기존 노출 조건과 동일하게 MemberContentPreference.isAdultContentVisible == true인 회원에게만 노출한다.
  • 동일 점수의 경우 스냅샷 생성 시 저장한 랜덤 tie-breaker 기준으로 노출한다.
  • 커뮤니티 인기 점수는 ((0.5 * 좋아요 수) + (0.5 * 댓글 수) + (0.1 * 팔로우 수)) * 신규 부스트로 계산한다.
  • 점수는 최근 7일 데이터를 기반으로 계산한다.
  • 신규 부스트는 데뷔 후 10일 이내 1.5, 20일 이내 1.3, 30일 이내 1.2, 그 외 1을 적용한다.
  • 신규 부스트의 데뷔일은 Member.createdAt이 아니라 콘텐츠를 처음 공개한 날과 라이브를 한 날 중 빠른 날짜로 계산한다.
  • 점수는 KST 매일 06:00에 전날 23:59:59 KST 기준 데이터로 1회 갱신한다. 스케줄러는 Asia/Seoul zone의 KST 06:00 기준으로 실행한다.

Edge Cases

  • 댓글 불가 게시글도 댓글 수 0으로 점수 계산 대상에 포함한다.

8. Technical Constraints

  • Kotlin, Spring Boot 2.7.14, Java 17, Gradle Wrapper 구조를 유지한다.
  • 신규 구현 코드는 kr.co.vividnext.sodalive.v2 하위에 둔다.
  • 신규 코드는 클라이언트 공개 API 조립 계층과 재사용 가능한 추천 기능 계층을 분리한다.
  • 클라이언트에 공개되는 메인 홈 API 조립 계층은 kr.co.vividnext.sodalive.v2.api.home 하위에 둔다.
  • 홈 API 외부에서도 재사용 가능한 추천, 점수 계산, 노출 정책, 스냅샷, 캐시, 콘텐츠 조회 이력 기능은 kr.co.vividnext.sodalive.v2.recommend 하위에 둔다.
  • 의존 방향은 v2.api.home에서 v2.recommend를 호출하는 방향으로만 둔다. v2.recommendv2.api.home의 DTO나 application service에 의존하지 않는다.
  • v2.api.homev2.recommend 모두 필요한 범위에서 경량 헥사고날 아키텍처를 적용하고, 기본 하위 패키지는 application, domain, port, adapter, dto를 사용한다.
  • Controller는 adapter.in.web, application service/use case는 application, repository/cache/scheduler 구현은 adapter.out.*, application이 외부 조회/저장 구현에 의존하는 계약은 port.out에 둔다.
  • port.in은 여러 adapter에서 같은 use case를 재사용하거나 진입 계약을 명확히 해야 할 때만 둔다.
  • 정책, 점수 계산, 노출 조건, 스냅샷 모델처럼 인프라 의존이 없는 코드는 domain에 둔다.
  • kr.co.vividnext.sodalive.v2 외부 코드는 엔티티만 재활용하고, Controller/Service/Repository/DTO는 신규 작성한다.
  • 기존 엔티티 후보는 Member, LiveRoom, AudioContent, AudioContentBanner, CreatorFollowing, CreatorCommunity, CreatorCommunityLike, CreatorCommunityComment, CreatorCheers, ChannelDonationMessage, AudioContentComment, AudioContentLike, ChatCharacter 등이다.
  • 조회 구현은 복잡도에 맞춰 선택한다. 단순 id 조회, 단건 조회, 명확한 조건 조회는 Spring Data JPA 기본 메서드 또는 @Query를 사용할 수 있고, 동적 조건/집계/서브쿼리/복합 정렬이 필요한 경우 QueryDSL을 사용한다.
  • 홈 추천 조회에는 공통 차단 필터를 적용해 내가 차단했거나 나를 차단한 크리에이터의 데이터를 제외한다.
  • 커뮤니티 게시글 조회에는 비공개 제외, 유료 글 제외, 핀 고정 글 제외, 성인 노출 조건(MemberContentPreference.isAdultContentVisible)을 공통 적용한다.
  • 일 1회 갱신 섹션은 조회 시점마다 무거운 집계를 하지 않도록 집계 테이블 또는 스냅샷 엔티티를 신규로 둔다.
  • 랜덤 정렬이 필요한 섹션은 성능을 고려해 후보군 축소 후 랜덤화하거나 스냅샷 생성 시 랜덤 tie-breaker 값을 저장한다.
  • 일 1회 갱신 스냅샷은 후보를 application/service 메모리로 모두 불러와 점수를 계산하지 않는다. DB 조회에서 모든 적격 후보의 최종 점수와 랜덤 tie-breaker를 계산한 뒤 score desc, randomTieBreaker asc 기준으로 정렬하고, 그 이후에만 최종 저장 개수 limit을 적용한다.
  • 최종 점수 계산 전 candidate pre-limit, 랜덤 후보 컷오프, 임의 2배수 선제 제한은 정확한 top 후보를 누락할 수 있으므로 금지한다.
  • DB score expression과 Kotlin RecommendationScorePolicy는 동일한 RecommendationScoreSpec의 가중치/부스트 구간 상수를 공유해 산식 drift를 방지한다.
  • 스냅샷 최종 저장 개수는 동점자 랜덤 노출 여지를 확보하기 위해 홈 첫 화면 노출 수의 2배인 AI 캐릭터 최대 20개, 최근 응원이 많은 크리에이터 최대 16개, 인기 커뮤니티 최대 20개로 한다. 단, 이 숫자는 최종 점수 계산과 동점 랜덤 정렬 이후 적용하는 저장 limit이며 candidate pre-limit가 아니다.
  • 공개 시간은 UTC 기준 응답을 원칙으로 한다.
  • 응답 DTO의 enum 값은 앱 다국어 처리를 위해 안정적인 영문 code로 내려준다.
  • 기존 API 스키마는 변경하지 않고 신규 v2 endpoint로 분리한다.

9. Metrics

  • 메인 홈 API 성공률과 응답 시간
  • 섹션별 빈 응답 비율
  • 전체보기 API 조회 수
  • 추천 섹션별 클릭률
  • 장르 추천 크리에이터 동시 팔로우 요청 수와 성공 수
  • 콘텐츠 조회 이력 기록 성공률
  • 일 배치 집계 성공/실패 수
  • 집계 스냅샷 생성 소요 시간

10. Decisions

  • 실제 데뷔일을 계산할 첫 공개 콘텐츠와 첫 라이브가 모두 없는 크리에이터는 Phase 2 스냅샷 후보에서 제외한다.
  • Phase 2 점수 기반 스냅샷은 DB-side exact scoring으로 계산한다. service는 기준 시각 계산과 snapshot replace만 담당하고, 최종 점수 산식/정렬/limit은 repository query에서 처리한다.

  • docs/prd/sample-prd.md
  • docs/agent-guides/작업절차.md
  • docs/agent-guides/문서유지보수.md