From f9bc0ffe99013f754339710ce562020c148c6df6 Mon Sep 17 00:00:00 2001 From: Klaus Date: Mon, 8 Jun 2026 20:45:45 +0900 Subject: [PATCH] =?UTF-8?q?docs(ranking):=20=ED=99=88=20API=20=ED=8C=A8?= =?UTF-8?q?=ED=82=A4=EC=A7=80=20=EA=B3=84=ED=9A=8D=EC=9D=84=20=EA=B0=B1?= =?UTF-8?q?=EC=8B=A0=ED=95=9C=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/20260608_크리에이터_랭킹/plan-task.md | 52 +++++++++++++--------- docs/20260608_크리에이터_랭킹/prd.md | 8 ++-- 2 files changed, 35 insertions(+), 25 deletions(-) diff --git a/docs/20260608_크리에이터_랭킹/plan-task.md b/docs/20260608_크리에이터_랭킹/plan-task.md index 98182e47..30460441 100644 --- a/docs/20260608_크리에이터_랭킹/plan-task.md +++ b/docs/20260608_크리에이터_랭킹/plan-task.md @@ -4,7 +4,7 @@ **Goal:** 홈 내부 랭킹 탭에서 `GET /api/v2/home/rankings/creators`로 KST 기준 지난 주 크리에이터 랭킹 상위 20명을 조회한다. -**Architecture:** 공개 endpoint는 home 하위 URL을 사용하지만 구현 코드는 추천 기능과 분리된 `kr.co.vividnext.sodalive.v2.ranking` 하위에 둔다. 주간 스냅샷 생성 작업이 KST 기간을 UTC DB 조회 조건으로 변환해 원천 데이터를 집계하고, 조회 API는 최신 완료 주차 스냅샷만 읽어 응답을 조립한다. +**Architecture:** 공개 endpoint는 home 하위 URL을 사용하고, 클라이언트 API 표면(Controller, API 조합 Facade, DTO)은 기존 홈 API 관례에 맞춰 `kr.co.vividnext.sodalive.v2.api.home` 하위에 둔다. 랭킹 기능 본체(domain/application/port/persistence/scheduler)는 추천 기능과 분리된 `kr.co.vividnext.sodalive.v2.ranking` 하위에 둔다. 주간 스냅샷 생성 작업이 KST 기간을 UTC DB 조회 조건으로 변환해 원천 데이터를 집계하고, 조회 API는 최신 완료 주차 스냅샷만 읽어 응답을 조립한다. **Tech Stack:** Kotlin, Spring Boot 2.7.14, Java 17, Spring Data JPA, QueryDSL 또는 native SQL, JUnit 5, Gradle Wrapper @@ -13,7 +13,8 @@ ## 0. 구현 전 확정 사항 - API endpoint: `GET /api/v2/home/rankings/creators` -- 구현 패키지: `kr.co.vividnext.sodalive.v2.ranking` +- 랭킹 기능 본체 패키지: `kr.co.vividnext.sodalive.v2.ranking` +- 홈 공개 API 조립 패키지: `kr.co.vividnext.sodalive.v2.api.home` - 집계 기간: 조회/스냅샷 생성 시점 기준 KST 지난 주 월요일 00:00:00 이상, 이번 주 월요일 00:00:00 미만 - DB 조회 기간: KST 집계 기간을 UTC 기준 `LocalDateTime` 또는 프로젝트 표준 시간 타입으로 변환한 기간 - 스냅샷 생성 스케줄 후보: 매주 월요일 KST 07:30, `@Scheduled(cron = "0 30 7 * * MON", zone = "Asia/Seoul")` @@ -46,9 +47,12 @@ - Create: `src/main/kotlin/kr/co/vividnext/sodalive/v2/ranking/port/out/CreatorRankingSnapshotPort.kt` - Create: `src/main/kotlin/kr/co/vividnext/sodalive/v2/ranking/port/out/CreatorRankingBlockPort.kt` -### 신규 API / scheduler / persistence -- Create: `src/main/kotlin/kr/co/vividnext/sodalive/v2/ranking/adapter/in/web/CreatorRankingController.kt` -- Create: `src/main/kotlin/kr/co/vividnext/sodalive/v2/ranking/adapter/in/web/CreatorRankingResponse.kt` +### 신규 홈 API 조립 계층 +- Create: `src/main/kotlin/kr/co/vividnext/sodalive/v2/api/home/adapter/in/web/CreatorRankingController.kt` +- Create: `src/main/kotlin/kr/co/vividnext/sodalive/v2/api/home/application/HomeCreatorRankingFacade.kt` +- Create: `src/main/kotlin/kr/co/vividnext/sodalive/v2/api/home/dto/ranking/CreatorRankingResponse.kt` + +### 신규 scheduler / persistence - Create: `src/main/kotlin/kr/co/vividnext/sodalive/v2/ranking/adapter/out/scheduler/CreatorRankingSnapshotScheduler.kt` - Create: `src/main/kotlin/kr/co/vividnext/sodalive/v2/ranking/adapter/out/persistence/CreatorRankingSnapshot.kt` - Create: `src/main/kotlin/kr/co/vividnext/sodalive/v2/ranking/adapter/out/persistence/CreatorRankingSnapshotRepository.kt` @@ -67,7 +71,7 @@ - Create: `src/test/kotlin/kr/co/vividnext/sodalive/v2/ranking/application/CreatorRankingQueryServiceTest.kt` - Create: `src/test/kotlin/kr/co/vividnext/sodalive/v2/ranking/adapter/out/persistence/DefaultCreatorRankingAggregationRepositoryTest.kt` - Create: `src/test/kotlin/kr/co/vividnext/sodalive/v2/ranking/adapter/out/persistence/DefaultCreatorRankingSnapshotRepositoryTest.kt` -- Create: `src/test/kotlin/kr/co/vividnext/sodalive/v2/ranking/adapter/in/web/CreatorRankingControllerTest.kt` +- Create: `src/test/kotlin/kr/co/vividnext/sodalive/v2/api/home/CreatorRankingControllerTest.kt` --- @@ -225,7 +229,7 @@ - 실패 확인: `./gradlew test --tests kr.co.vividnext.sodalive.v2.ranking.application.CreatorRankingQueryServiceTest` - GREEN: 최신 스냅샷 후보를 최종 점수 내림차순과 동점 랜덤 정렬로 최대 20명 선정하고, 직전 스냅샷 순위와 비교해 순위 변화를 계산한다. - REFACTOR: 동점 랜덤으로 인해 같은 동점 구간의 순위 변화가 조회마다 달라질 수 있음을 테스트에서 허용 범위로 표현한다. - - 기대 결과: API 응답에 필요한 `showRankChange`와 item 목록이 application service에서 완성된다. + - 기대 결과: 홈 API Facade가 사용할 `showRankChange`와 item 목록이 ranking application service에서 완성된다. - [ ] **Task 5.2: 차단 관계 마스킹 port 구현** - Files: @@ -239,25 +243,27 @@ - REFACTOR: 기본 이미지 URL은 기존 프로젝트 상수/설정이 있으면 재사용하고, 없으면 ranking service 내부 상수로 분리한다. - 기대 결과: 차단 관계가 있어도 순위 row 수는 유지되고 개인 식별 정보만 가려진다. -### Phase 6: API endpoint와 DTO +### Phase 6: 홈 API endpoint, Facade, DTO -- [ ] **Task 6.1: 랭킹 조회 DTO와 Controller 추가** +- [ ] **Task 6.1: 랭킹 조회 DTO, 홈 API Facade, Controller 추가** - Files: - - Create: `src/main/kotlin/kr/co/vividnext/sodalive/v2/ranking/adapter/in/web/CreatorRankingResponse.kt` - - Create: `src/main/kotlin/kr/co/vividnext/sodalive/v2/ranking/adapter/in/web/CreatorRankingController.kt` - - Test: `src/test/kotlin/kr/co/vividnext/sodalive/v2/ranking/adapter/in/web/CreatorRankingControllerTest.kt` + - Create: `src/main/kotlin/kr/co/vividnext/sodalive/v2/api/home/dto/ranking/CreatorRankingResponse.kt` + - Create: `src/main/kotlin/kr/co/vividnext/sodalive/v2/api/home/application/HomeCreatorRankingFacade.kt` + - Create: `src/main/kotlin/kr/co/vividnext/sodalive/v2/api/home/adapter/in/web/CreatorRankingController.kt` + - Test: `src/test/kotlin/kr/co/vividnext/sodalive/v2/api/home/CreatorRankingControllerTest.kt` - RED: `GET /api/v2/home/rankings/creators`가 `showRankChange`, `items[].rank`, `rankChange`, `isNew`, `creatorId`, `nickname`, `profileImageUrl`만 반환하고 날짜와 `finalScore`를 반환하지 않는 controller 테스트를 작성한다. - - 실패 확인: `./gradlew test --tests kr.co.vividnext.sodalive.v2.ranking.adapter.in.web.CreatorRankingControllerTest` - - GREEN: controller와 response DTO를 구현하고 `CreatorRankingQueryService`를 호출한다. - - REFACTOR: URL은 home 하위지만 코드 패키지는 `v2.ranking`에 유지한다. + - 실패 확인: `./gradlew test --tests kr.co.vividnext.sodalive.v2.api.home.CreatorRankingControllerTest` + - GREEN: controller, API Facade, response DTO를 구현하고 Facade가 `CreatorRankingQueryService`를 호출해 홈 API 응답으로 변환한다. + - REFACTOR: URL과 클라이언트 API 표면은 `v2.api.home` 하위에 두고, 랭킹 DTO는 `v2.api.home.dto.ranking` 하위에 둔다. 랭킹 계산/조회 본체는 `v2.ranking`에 유지한다. - 기대 결과: 클라이언트 홈 랭킹 탭에서 사용할 공개 API 계약이 테스트로 고정된다. - [ ] **Task 6.2: 인증/비인증 조회와 차단 마스킹 연결** - Files: - - Modify: `src/main/kotlin/kr/co/vividnext/sodalive/v2/ranking/adapter/in/web/CreatorRankingController.kt` - - Test: `src/test/kotlin/kr/co/vividnext/sodalive/v2/ranking/adapter/in/web/CreatorRankingControllerTest.kt` + - Modify: `src/main/kotlin/kr/co/vividnext/sodalive/v2/api/home/adapter/in/web/CreatorRankingController.kt` + - Modify: `src/main/kotlin/kr/co/vividnext/sodalive/v2/api/home/application/HomeCreatorRankingFacade.kt` + - Test: `src/test/kotlin/kr/co/vividnext/sodalive/v2/api/home/CreatorRankingControllerTest.kt` - RED: 비회원 조회는 기본 랭킹을 반환하고, 인증 회원 조회는 차단 관계 마스킹을 적용하는 controller 테스트를 작성한다. - - 실패 확인: `./gradlew test --tests kr.co.vividnext.sodalive.v2.ranking.adapter.in.web.CreatorRankingControllerTest` + - 실패 확인: `./gradlew test --tests kr.co.vividnext.sodalive.v2.api.home.CreatorRankingControllerTest` - GREEN: 기존 인증 주입 패턴을 확인해 member nullable 흐름을 service에 전달한다. - REFACTOR: 기존 API 응답 wrapper 관례와 상태 코드를 맞춘다. - 기대 결과: 인증 여부에 따라 차단 마스킹만 달라지고 endpoint 계약은 동일하다. @@ -280,15 +286,17 @@ - Files: - Verify: `src/main/kotlin/kr/co/vividnext/sodalive/v2/ranking/**` - Verify: `src/test/kotlin/kr/co/vividnext/sodalive/v2/ranking/**` + - Verify: `src/main/kotlin/kr/co/vividnext/sodalive/v2/api/home/**` + - Verify: `src/test/kotlin/kr/co/vividnext/sodalive/v2/api/home/**` - Modify: `docs/20260608_크리에이터_랭킹/plan-task.md` - RED: 테스트 작성 예외. `TDD 예외 사유`: 구현 완료 후 회귀 검증 task다. - 대체 검증 방법: - - `./gradlew test --tests 'kr.co.vividnext.sodalive.v2.ranking.*'` + - `./gradlew test --tests 'kr.co.vividnext.sodalive.v2.ranking.*' --tests 'kr.co.vividnext.sodalive.v2.api.home.*'` - `./gradlew ktlintCheck` - `./gradlew test` - GREEN: 실패하는 테스트가 있으면 해당 phase task로 돌아가 수정하고, 모든 명령을 통과시킨다. - REFACTOR: plan-task 하단 검증 기록에 실행 명령, 목적, 결과를 누적한다. - - 기대 결과: ranking 기능 단위 테스트, 포맷, 전체 회귀 테스트가 통과한다. + - 기대 결과: ranking 기능 본체와 홈 API 조립 계층 테스트, 포맷, 전체 회귀 테스트가 통과한다. --- @@ -300,9 +308,9 @@ - Feature D: Task 1.2, Task 3.3, Task 4.1에서 채널 후원 캔/건수와 최상위 팬 Talk 집계를 검증한다. - Feature E: Task 1.2, Task 3.4, Task 4.1에서 최종 팔로우 수와 `createdAt`/`updatedAt` 기반 팔로우 증가 수를 검증한다. - Feature F: Task 1.2, Task 4.1, Task 5.1에서 raw value 최종 점수, 1점 미만 제외, 20위 동점 후보 저장, 동점 랜덤 조회를 검증한다. -- Feature G: Task 5.1, Task 5.2, Task 6.1, Task 6.2에서 API endpoint, 응답 스키마, 순위 변화, 신규 진입, 차단 마스킹을 검증한다. +- Feature G: Task 5.1, Task 5.2에서 ranking 조회 결과와 차단 마스킹을 검증하고, Task 6.1, Task 6.2에서 홈 API endpoint, 응답 스키마, 인증/비인증 연결을 검증한다. - Feature H: Task 2.1, Task 2.2, Task 4.1, Task 4.2, Task 4.3에서 주간 스냅샷 저장, 스케줄, 클러스터 단일 실행 lock을 검증한다. -- Feature I: 모든 task에서 `v2.ranking` 패키지 경계를 유지하고, Task 6.1에서 endpoint만 home 하위로 둔다. +- Feature I: Phase 5의 ranking 기능 본체는 `v2.ranking` 패키지 경계를 유지하고, Phase 6의 클라이언트 API 표면은 `v2.api.home` 하위에 둔다. --- diff --git a/docs/20260608_크리에이터_랭킹/prd.md b/docs/20260608_크리에이터_랭킹/prd.md index 047a1806..189b14b7 100644 --- a/docs/20260608_크리에이터_랭킹/prd.md +++ b/docs/20260608_크리에이터_랭킹/prd.md @@ -224,13 +224,14 @@ #### Requirements - 랭킹 계산과 조회는 Controller나 Facade 내부에 직접 구현하지 않고 별도 application/domain 컴포넌트로 분리한다. -- 크리에이터 랭킹은 추천 기능과 독립된 성격이므로 `v2.recommend`가 아니라 별도 `kr.co.vividnext.sodalive.v2.ranking` 하위 패키지에 작성한다. +- 크리에이터 랭킹 기능 본체는 추천 기능과 독립된 성격이므로 `v2.recommend`가 아니라 별도 `kr.co.vividnext.sodalive.v2.ranking` 하위 패키지에 작성한다. - 예시 컴포넌트는 다음 책임을 갖는다. - 기간 계산 정책: KST 기준 지난 주 기간을 산출한다. - 점수 정책: 원천 지표의 raw value에 가중치를 적용해 카테고리/최종 점수를 계산한다. - 집계 포트: `UseCan`, 콘텐츠 반응, `CreatorCheers`, `CreatorFollowing` 원천 데이터를 조회한다. - 스냅샷 생성 서비스: 원천 지표를 집계하고 랭킹 스냅샷을 저장한다. - - 조회 서비스: 저장된 스냅샷을 상위 20명 응답으로 조립한다. + - 조회 서비스: 저장된 스냅샷을 상위 20명 ranking 조회 결과로 조립한다. + - 홈 API 조합 Facade: ranking 조회 결과를 클라이언트 공개 응답 DTO로 변환한다. - 추후 캐싱을 추가할 수 있도록 조회 서비스는 스냅샷 조회 포트와 캐시 포트를 분리할 수 있는 경계를 둔다. #### Edge Cases @@ -241,8 +242,9 @@ ## 8. Technical Constraints - Kotlin, Spring Boot 2.7.14, Java 17, Gradle Wrapper 구조를 유지한다. -- 신규 공개 API 구현 코드는 기존 v2 패키지 관례를 따라 `kr.co.vividnext.sodalive.v2.ranking` 하위에 작성한다. +- 랭킹 계산, 스냅샷 생성, 스냅샷 조회, 차단 마스킹 등 기능 본체는 `kr.co.vividnext.sodalive.v2.ranking` 하위에 작성한다. - 클라이언트 endpoint는 홈 내부 랭킹 탭에서 호출하므로 `/api/v2/home/rankings/creators`를 사용한다. +- 클라이언트 공개 API 표면인 Controller와 API 조합 Facade는 기존 홈 API 관례를 따라 `kr.co.vividnext.sodalive.v2.api.home` 하위에 작성하고, 크리에이터 랭킹 응답 DTO는 `kr.co.vividnext.sodalive.v2.api.home.dto.ranking` 하위에 작성한다. - 기존 엔티티 후보는 `UseCan`, `CanUsage`, `AudioContent`, `AudioContentLike`, `AudioContentComment`, `CreatorCheers`, `CreatorFollowing`, `Member` 등이다. - 기존 공개 API 스키마는 변경하지 않는다. - 계산 기간은 서버 기본 timezone이 아니라 명시적인 KST 기준으로 산출하고, DB 조회 시에는 UTC 기간으로 변환한다.