From eded4ac39a05b11e46113b0c0ac14aa45ff7a6d9 Mon Sep 17 00:00:00 2001 From: Klaus Date: Wed, 17 Jun 2026 22:22:41 +0900 Subject: [PATCH] =?UTF-8?q?docs(creator):=20=EC=B1=84=EB=84=90=20=ED=99=88?= =?UTF-8?q?=20API=20=EA=B5=AC=EC=A1=B0=20=EC=A0=95=EB=A0=AC=20=EA=B3=84?= =?UTF-8?q?=ED=9A=8D=EC=9D=84=20=EC=B6=94=EA=B0=80=ED=95=9C=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../plan-task.md | 285 ++++++++++++++++++ .../prd.md | 130 ++++++++ 2 files changed, 415 insertions(+) create mode 100644 docs/20260617_크리에이터_채널_홈_API_구조정렬/plan-task.md create mode 100644 docs/20260617_크리에이터_채널_홈_API_구조정렬/prd.md diff --git a/docs/20260617_크리에이터_채널_홈_API_구조정렬/plan-task.md b/docs/20260617_크리에이터_채널_홈_API_구조정렬/plan-task.md new file mode 100644 index 00000000..60b1a7ff --- /dev/null +++ b/docs/20260617_크리에이터_채널_홈_API_구조정렬/plan-task.md @@ -0,0 +1,285 @@ +# 크리에이터 채널 홈 API 구조 정렬 Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use `superpowers:subagent-driven-development` 또는 `superpowers:executing-plans`로 task 단위 구현을 진행한다. 각 단계는 체크박스(`- [ ]`)로 진행 상태를 갱신한다. + +**Goal:** `GET /api/v2/creator-channels/{creatorId}/home`의 공개 계약을 보존하면서 홈 API 공개 조립 계층을 `v2.api.creator.channel.home`으로 옮기고 도메인 조회 계층을 API 패키지 밖으로 정렬한다. + +**Architecture:** Controller, facade, response DTO는 `kr.co.vividnext.sodalive.v2.api.creator.channel.home` 하위에 두고, HTTP 계약과 공개 응답 변환만 담당한다. 조회 service, 순수 정책, domain model, port, repository는 `kr.co.vividnext.sodalive.v2.creator.channel.home` 하위에 두며 `v2.api.*`를 import하지 않는다. 기존 endpoint와 DTO 필드명은 그대로 유지하고, 기존 `v2.creator.channel.adapter.in.web.CreatorChannelHomeController`는 이동 후 남기지 않아 Spring mapping 충돌을 방지한다. + +**Tech Stack:** Kotlin, Spring Boot 2.7.14, Java 17, Spring MVC, Spring Data JPA, QueryDSL, JUnit 5, MockMvc, Gradle Wrapper, ktlint + +--- + +## 0. 구현 전 확정 사항 + +- 작업 성격: 동작 보존 리팩토링 +- 기존 공개 endpoint: `GET /api/v2/creator-channels/{creatorId}/home` +- 기존 인증 정책: 인증 회원만 조회 가능, 비회원은 `common.error.bad_credentials` 계열 오류 +- 공개 API 조립 패키지: + - `kr.co.vividnext.sodalive.v2.api.creator.channel.home.adapter.in.web` + - `kr.co.vividnext.sodalive.v2.api.creator.channel.home.application` + - `kr.co.vividnext.sodalive.v2.api.creator.channel.home.dto` +- 도메인 조회 패키지: + - `kr.co.vividnext.sodalive.v2.creator.channel.home.application` + - `kr.co.vividnext.sodalive.v2.creator.channel.home.domain` + - `kr.co.vividnext.sodalive.v2.creator.channel.home.port.out` + - `kr.co.vividnext.sodalive.v2.creator.channel.home.adapter.out.persistence` +- 의존 방향: `v2.api.creator.channel.home -> v2.creator.channel.home` +- 금지 사항: + - endpoint 변경 금지 + - 응답 필드명/의미 변경 금지 + - 기능 추가 금지 + - 라이브 탭 API 동작 변경 금지 + - 불필요한 공용화 금지 + +## 1. 현재 공개 계약 + +현재 `src/main/kotlin/kr/co/vividnext/sodalive/v2/creator/channel/dto/CreatorChannelHomeResponse.kt` 기준 최상위 응답 필드는 아래와 같다. 구조 정렬 후에도 필드명과 의미를 유지한다. + +```kotlin +data class CreatorChannelHomeResponse( + val creator: CreatorChannelCreatorResponse, + val currentLive: CreatorChannelLiveResponse?, + val latestAudioContent: CreatorChannelAudioContentResponse?, + val channelDonations: List, + val notices: List, + val schedules: List, + val audioContents: List, + val series: List, + val communities: List, + val fanTalk: CreatorChannelFanTalkSummaryResponse, + val introduce: String, + val activity: CreatorChannelActivityResponse, + val sns: CreatorChannelSnsResponse +) +``` + +아래 `@JsonProperty` 기반 boolean 필드명은 이동 후에도 유지한다. + +- `creator.isAiChatAvailable` +- `creator.isDmAvailable` +- `creator.isFollow` +- `creator.isNotify` +- `currentLive.isAdult` +- `latestAudioContent.isAdult` +- `latestAudioContent.isPointAvailable` +- `latestAudioContent.isFirstContent` +- `latestAudioContent.isOriginalSeries` +- `latestAudioContent.isOwned` +- `latestAudioContent.isRented` +- `audioContents[*].isAdult` +- `audioContents[*].isPointAvailable` +- `audioContents[*].isFirstContent` +- `audioContents[*].isOriginalSeries` +- `audioContents[*].isOwned` +- `audioContents[*].isRented` +- `series[*].isNew` +- `series[*].isOriginal` + +## 2. 파일 구조 계획 + +### 공개 API 조립 계층 +- Move: `src/main/kotlin/kr/co/vividnext/sodalive/v2/creator/channel/adapter/in/web/CreatorChannelHomeController.kt` -> `src/main/kotlin/kr/co/vividnext/sodalive/v2/api/creator/channel/home/adapter/in/web/CreatorChannelHomeController.kt` +- Create: `src/main/kotlin/kr/co/vividnext/sodalive/v2/api/creator/channel/home/application/CreatorChannelHomeFacade.kt` +- Move: `src/main/kotlin/kr/co/vividnext/sodalive/v2/creator/channel/dto/CreatorChannelHomeResponse.kt` -> `src/main/kotlin/kr/co/vividnext/sodalive/v2/api/creator/channel/home/dto/CreatorChannelHomeResponse.kt` + +### 도메인 조회 계층 +- Move: `src/main/kotlin/kr/co/vividnext/sodalive/v2/creator/channel/application/CreatorChannelHomeQueryService.kt` -> `src/main/kotlin/kr/co/vividnext/sodalive/v2/creator/channel/home/application/CreatorChannelHomeQueryService.kt` +- Move: `src/main/kotlin/kr/co/vividnext/sodalive/v2/creator/channel/domain/CreatorChannelHome.kt` -> `src/main/kotlin/kr/co/vividnext/sodalive/v2/creator/channel/home/domain/CreatorChannelHome.kt` +- Move: `src/main/kotlin/kr/co/vividnext/sodalive/v2/creator/channel/domain/CreatorChannelHomeQueryPolicy.kt` -> `src/main/kotlin/kr/co/vividnext/sodalive/v2/creator/channel/home/domain/CreatorChannelHomeQueryPolicy.kt` +- Move: `src/main/kotlin/kr/co/vividnext/sodalive/v2/creator/channel/port/out/CreatorChannelHomeQueryPort.kt` -> `src/main/kotlin/kr/co/vividnext/sodalive/v2/creator/channel/home/port/out/CreatorChannelHomeQueryPort.kt` +- Move: `src/main/kotlin/kr/co/vividnext/sodalive/v2/creator/channel/adapter/out/persistence/CreatorChannelHomeQueryRepository.kt` -> `src/main/kotlin/kr/co/vividnext/sodalive/v2/creator/channel/home/adapter/out/persistence/CreatorChannelHomeQueryRepository.kt` +- Move: `src/main/kotlin/kr/co/vividnext/sodalive/v2/creator/channel/adapter/out/persistence/DefaultCreatorChannelHomeQueryRepository.kt` -> `src/main/kotlin/kr/co/vividnext/sodalive/v2/creator/channel/home/adapter/out/persistence/DefaultCreatorChannelHomeQueryRepository.kt` + +### 테스트 +- Move: `src/test/kotlin/kr/co/vividnext/sodalive/v2/creator/channel/adapter/in/web/CreatorChannelHomeControllerTest.kt` -> `src/test/kotlin/kr/co/vividnext/sodalive/v2/api/creator/channel/home/adapter/in/web/CreatorChannelHomeControllerTest.kt` +- Create: `src/test/kotlin/kr/co/vividnext/sodalive/v2/api/creator/channel/home/application/CreatorChannelHomeFacadeTest.kt` +- Move: `src/test/kotlin/kr/co/vividnext/sodalive/v2/creator/channel/application/CreatorChannelHomeQueryServiceTest.kt` -> `src/test/kotlin/kr/co/vividnext/sodalive/v2/creator/channel/home/application/CreatorChannelHomeQueryServiceTest.kt` +- Move: `src/test/kotlin/kr/co/vividnext/sodalive/v2/creator/channel/domain/CreatorChannelHomeQueryPolicyTest.kt` -> `src/test/kotlin/kr/co/vividnext/sodalive/v2/creator/channel/home/domain/CreatorChannelHomeQueryPolicyTest.kt` +- Move: `src/test/kotlin/kr/co/vividnext/sodalive/v2/creator/channel/adapter/out/persistence/DefaultCreatorChannelHomeQueryRepositoryTest.kt` -> `src/test/kotlin/kr/co/vividnext/sodalive/v2/creator/channel/home/adapter/out/persistence/DefaultCreatorChannelHomeQueryRepositoryTest.kt` + +### 문서 산출물 +- Modify: `docs/20260617_크리에이터_채널_홈_API_구조정렬/plan-task.md` + +--- + +### Phase 1: 현재 계약 고정과 이동 전 실패 확인 + +- [ ] **Task 1.1: controller 테스트를 새 API 패키지 기준으로 이동해 실패 확인** + - Files: + - Move: `src/test/kotlin/kr/co/vividnext/sodalive/v2/creator/channel/adapter/in/web/CreatorChannelHomeControllerTest.kt` -> `src/test/kotlin/kr/co/vividnext/sodalive/v2/api/creator/channel/home/adapter/in/web/CreatorChannelHomeControllerTest.kt` + - Verify: `src/main/kotlin/kr/co/vividnext/sodalive/v2/creator/channel/adapter/in/web/CreatorChannelHomeController.kt` + - RED: 테스트 package와 import를 새 controller 위치인 `kr.co.vividnext.sodalive.v2.api.creator.channel.home.adapter.in.web.CreatorChannelHomeController` 기준으로 변경한다. 기존 endpoint `/api/v2/creator-channels/1/home`, 비회원 거부, 대표 JSON field path 검증은 유지한다. + - 실패 확인: + - Run: `./gradlew test --tests kr.co.vividnext.sodalive.v2.api.creator.channel.home.adapter.in.web.CreatorChannelHomeControllerTest` + - Expected: 새 controller 패키지가 아직 없어 컴파일 실패한다. + - GREEN: 아직 구현하지 않는다. 이 task는 이동 대상 controller 부재로 RED를 확인하는 단계다. + - REFACTOR: 없음. + - 기대 결과: 공개 API 조립 계층 이동 필요성이 테스트 실패로 고정된다. + +- [ ] **Task 1.2: facade 테스트를 추가해 공개 응답 변환 책임을 고정** + - Files: + - Create: `src/test/kotlin/kr/co/vividnext/sodalive/v2/api/creator/channel/home/application/CreatorChannelHomeFacadeTest.kt` + - Verify: `src/main/kotlin/kr/co/vividnext/sodalive/v2/api/creator/channel/home/application/CreatorChannelHomeFacade.kt` + - RED: `CreatorChannelHomeFacade`가 `CreatorChannelHomeQueryService.getHome(...)` 결과를 `CreatorChannelHomeResponse`로 변환하고 기존 필드명 의미를 유지하는지 검증하는 테스트를 작성한다. + - 실패 확인: + - Run: `./gradlew test --tests kr.co.vividnext.sodalive.v2.api.creator.channel.home.application.CreatorChannelHomeFacadeTest` + - Expected: `CreatorChannelHomeFacade` 미존재로 컴파일 실패한다. + - GREEN: 아직 구현하지 않는다. 이 task는 facade 책임 부재로 RED를 확인하는 단계다. + - REFACTOR: 없음. + - 기대 결과: API 조립 계층이 service 대신 response DTO 변환 책임을 갖는다는 기준이 고정된다. + +--- + +### Phase 2: 공개 API 조립 계층 이동 + +- [ ] **Task 2.1: response DTO를 `v2.api.creator.channel.home.dto`로 이동** + - Files: + - Move: `src/main/kotlin/kr/co/vividnext/sodalive/v2/creator/channel/dto/CreatorChannelHomeResponse.kt` -> `src/main/kotlin/kr/co/vividnext/sodalive/v2/api/creator/channel/home/dto/CreatorChannelHomeResponse.kt` + - Modify: `src/test/kotlin/kr/co/vividnext/sodalive/v2/api/creator/channel/home/application/CreatorChannelHomeFacadeTest.kt` + - Modify: `src/test/kotlin/kr/co/vividnext/sodalive/v2/api/creator/channel/home/adapter/in/web/CreatorChannelHomeControllerTest.kt` + - RED: Task 1.1, Task 1.2에서 response DTO 새 package import 기준 컴파일 실패를 확인한 상태를 유지한다. + - GREEN: DTO 파일 package를 `kr.co.vividnext.sodalive.v2.api.creator.channel.home.dto`로 변경하고, domain model import는 새 도메인 패키지 이동 전까지 기존 경로를 임시로 사용한다. + - 통과 확인: + - Run: `./gradlew test --tests kr.co.vividnext.sodalive.v2.api.creator.channel.home.application.CreatorChannelHomeFacadeTest` + - Expected: facade가 아직 없으면 실패가 유지된다. DTO 자체 import 오류는 해결되어야 한다. + - REFACTOR: `@JsonProperty`가 이동 중 누락되지 않았는지 파일 diff로 확인한다. + - 기대 결과: 공개 응답 DTO가 API 조립 계층에 위치한다. + +- [ ] **Task 2.2: `CreatorChannelHomeFacade`를 추가** + - Files: + - Create: `src/main/kotlin/kr/co/vividnext/sodalive/v2/api/creator/channel/home/application/CreatorChannelHomeFacade.kt` + - Test: `src/test/kotlin/kr/co/vividnext/sodalive/v2/api/creator/channel/home/application/CreatorChannelHomeFacadeTest.kt` + - RED: Task 1.2의 facade 미존재 실패를 사용한다. + - GREEN: `CreatorChannelHomeFacade`를 추가하고 `CreatorChannelHomeQueryService`를 호출한 뒤 `CreatorChannelHomeResponse.from(...)`으로 변환한다. + - 통과 확인: + - Run: `./gradlew test --tests kr.co.vividnext.sodalive.v2.api.creator.channel.home.application.CreatorChannelHomeFacadeTest` + - Expected: PASS + - REFACTOR: facade에 조회 정책이나 repository 접근이 들어가지 않았는지 확인한다. + - 기대 결과: 공개 API 조립 계층의 응답 변환 책임이 controller에서 facade로 이동한다. + +- [ ] **Task 2.3: controller를 `v2.api.creator.channel.home.adapter.in.web`으로 이동** + - Files: + - Move: `src/main/kotlin/kr/co/vividnext/sodalive/v2/creator/channel/adapter/in/web/CreatorChannelHomeController.kt` -> `src/main/kotlin/kr/co/vividnext/sodalive/v2/api/creator/channel/home/adapter/in/web/CreatorChannelHomeController.kt` + - Modify: `src/test/kotlin/kr/co/vividnext/sodalive/v2/api/creator/channel/home/adapter/in/web/CreatorChannelHomeControllerTest.kt` + - RED: Task 1.1의 새 controller package 미존재 실패를 사용한다. + - GREEN: controller package를 변경하고 직접 `CreatorChannelHomeQueryService` 대신 `CreatorChannelHomeFacade`를 주입한다. `@RequestMapping("/api/v2/creator-channels")`, `@GetMapping("/{creatorId}/home")`, `requireMember` 동작은 유지한다. + - 통과 확인: + - Run: `./gradlew test --tests kr.co.vividnext.sodalive.v2.api.creator.channel.home.adapter.in.web.CreatorChannelHomeControllerTest` + - Expected: PASS + - REFACTOR: 기존 경로에 `CreatorChannelHomeController.kt`가 남아 있지 않은지 확인한다. + - Run: `rg -n "class CreatorChannelHomeController|/\\{creatorId\\}/home" src/main/kotlin/kr/co/vividnext/sodalive/v2` + - Expected: home controller mapping은 새 API 패키지 controller 1건만 확인된다. + - 기대 결과: Spring mapping 충돌 없이 홈 API controller가 API 조립 계층에 위치한다. + +--- + +### Phase 3: 도메인 조회 계층 패키지 정렬 + +- [ ] **Task 3.1: domain model과 query policy를 `v2.creator.channel.home.domain`으로 이동** + - Files: + - Move: `src/main/kotlin/kr/co/vividnext/sodalive/v2/creator/channel/domain/CreatorChannelHome.kt` -> `src/main/kotlin/kr/co/vividnext/sodalive/v2/creator/channel/home/domain/CreatorChannelHome.kt` + - Move: `src/main/kotlin/kr/co/vividnext/sodalive/v2/creator/channel/domain/CreatorChannelHomeQueryPolicy.kt` -> `src/main/kotlin/kr/co/vividnext/sodalive/v2/creator/channel/home/domain/CreatorChannelHomeQueryPolicy.kt` + - Move: `src/test/kotlin/kr/co/vividnext/sodalive/v2/creator/channel/domain/CreatorChannelHomeQueryPolicyTest.kt` -> `src/test/kotlin/kr/co/vividnext/sodalive/v2/creator/channel/home/domain/CreatorChannelHomeQueryPolicyTest.kt` + - Modify: imports in moved API DTO, service, tests + - RED: 이동한 테스트 package를 새 domain package 기준으로 바꾼 뒤 기존 main class 미이동 상태의 컴파일 실패를 확인한다. + - 실패 확인: + - Run: `./gradlew test --tests kr.co.vividnext.sodalive.v2.creator.channel.home.domain.CreatorChannelHomeQueryPolicyTest` + - Expected: 새 domain package class 미존재로 컴파일 실패한다. + - GREEN: domain model과 policy package를 변경하고 import를 갱신한다. + - 통과 확인: + - Run: `./gradlew test --tests kr.co.vividnext.sodalive.v2.creator.channel.home.domain.CreatorChannelHomeQueryPolicyTest` + - Expected: PASS + - REFACTOR: domain model이 `kr.co.vividnext.sodalive.v2.api`를 import하지 않는지 확인한다. + - 기대 결과: 순수 domain 책임이 API 패키지 밖의 home 도메인 패키지에 위치한다. + +- [ ] **Task 3.2: port와 query service를 `v2.creator.channel.home` 하위로 이동** + - Files: + - Move: `src/main/kotlin/kr/co/vividnext/sodalive/v2/creator/channel/application/CreatorChannelHomeQueryService.kt` -> `src/main/kotlin/kr/co/vividnext/sodalive/v2/creator/channel/home/application/CreatorChannelHomeQueryService.kt` + - Move: `src/main/kotlin/kr/co/vividnext/sodalive/v2/creator/channel/port/out/CreatorChannelHomeQueryPort.kt` -> `src/main/kotlin/kr/co/vividnext/sodalive/v2/creator/channel/home/port/out/CreatorChannelHomeQueryPort.kt` + - Move: `src/test/kotlin/kr/co/vividnext/sodalive/v2/creator/channel/application/CreatorChannelHomeQueryServiceTest.kt` -> `src/test/kotlin/kr/co/vividnext/sodalive/v2/creator/channel/home/application/CreatorChannelHomeQueryServiceTest.kt` + - Modify: `src/main/kotlin/kr/co/vividnext/sodalive/v2/api/creator/channel/home/application/CreatorChannelHomeFacade.kt` + - RED: service 테스트 package와 imports를 새 경로로 바꾼 뒤 기존 main class 미이동 상태의 컴파일 실패를 확인한다. + - 실패 확인: + - Run: `./gradlew test --tests kr.co.vividnext.sodalive.v2.creator.channel.home.application.CreatorChannelHomeQueryServiceTest` + - Expected: 새 service/port package class 미존재로 컴파일 실패한다. + - GREEN: service와 port package를 변경하고 API facade가 새 service package를 import하도록 갱신한다. + - 통과 확인: + - Run: `./gradlew test --tests kr.co.vividnext.sodalive.v2.creator.channel.home.application.CreatorChannelHomeQueryServiceTest` + - Run: `./gradlew test --tests kr.co.vividnext.sodalive.v2.api.creator.channel.home.application.CreatorChannelHomeFacadeTest` + - Expected: PASS + - REFACTOR: service가 API DTO를 import하지 않는지 확인한다. + - 기대 결과: 도메인 application service가 API 조립 계층에 의존하지 않는다. + +- [ ] **Task 3.3: repository adapter를 `v2.creator.channel.home.adapter.out.persistence`로 이동** + - Files: + - Move: `src/main/kotlin/kr/co/vividnext/sodalive/v2/creator/channel/adapter/out/persistence/CreatorChannelHomeQueryRepository.kt` -> `src/main/kotlin/kr/co/vividnext/sodalive/v2/creator/channel/home/adapter/out/persistence/CreatorChannelHomeQueryRepository.kt` + - Move: `src/main/kotlin/kr/co/vividnext/sodalive/v2/creator/channel/adapter/out/persistence/DefaultCreatorChannelHomeQueryRepository.kt` -> `src/main/kotlin/kr/co/vividnext/sodalive/v2/creator/channel/home/adapter/out/persistence/DefaultCreatorChannelHomeQueryRepository.kt` + - Move: `src/test/kotlin/kr/co/vividnext/sodalive/v2/creator/channel/adapter/out/persistence/DefaultCreatorChannelHomeQueryRepositoryTest.kt` -> `src/test/kotlin/kr/co/vividnext/sodalive/v2/creator/channel/home/adapter/out/persistence/DefaultCreatorChannelHomeQueryRepositoryTest.kt` + - Modify: imports in service and tests + - RED: repository 테스트 package와 imports를 새 경로로 바꾼 뒤 기존 main class 미이동 상태의 컴파일 실패를 확인한다. + - 실패 확인: + - Run: `./gradlew test --tests kr.co.vividnext.sodalive.v2.creator.channel.home.adapter.out.persistence.DefaultCreatorChannelHomeQueryRepositoryTest` + - Expected: 새 repository package class 미존재로 컴파일 실패한다. + - GREEN: repository interface와 기본 구현체 package를 변경하고 port import를 새 경로로 갱신한다. + - 통과 확인: + - Run: `./gradlew test --tests kr.co.vividnext.sodalive.v2.creator.channel.home.adapter.out.persistence.DefaultCreatorChannelHomeQueryRepositoryTest` + - Expected: PASS + - REFACTOR: repository 조회 조건과 정렬 조건의 동작 변경이 diff에 포함되지 않았는지 확인한다. + - 기대 결과: persistence adapter가 home 도메인 패키지 하위에 위치하고 기존 조회 정책을 유지한다. + +--- + +### Phase 4: 의존 방향과 회귀 검증 + +- [ ] **Task 4.1: 도메인 패키지의 API 패키지 의존 여부 확인** + - Files: + - Verify: `src/main/kotlin/kr/co/vividnext/sodalive/v2/creator` + - Verify: `src/main/kotlin/kr/co/vividnext/sodalive/v2/live` + - Verify: `src/main/kotlin/kr/co/vividnext/sodalive/v2/content` + - Verify: `src/main/kotlin/kr/co/vividnext/sodalive/v2/series` + - RED: 해당 없음. 검색 기반 검증 task다. + - TDD 예외 사유: package import 방향 검증은 실패 테스트보다 정적 검색이 더 직접적인 검증이다. + - 대체 검증 방법: + - Run: `rg -n "kr\\.co\\.vividnext\\.sodalive\\.v2\\.api" src/main/kotlin/kr/co/vividnext/sodalive/v2/creator src/main/kotlin/kr/co/vividnext/sodalive/v2/live src/main/kotlin/kr/co/vividnext/sodalive/v2/content src/main/kotlin/kr/co/vividnext/sodalive/v2/series` + - Expected: 도메인 패키지에서 API 패키지 import 결과 0건 + - GREEN: 검색 결과가 있으면 API DTO 의존을 제거하고 domain model 또는 port record 의존으로 되돌린다. + - REFACTOR: 라이브 탭 API 패키지는 이번 범위에서 동작 변경하지 않았는지 diff로 확인한다. + - 기대 결과: 의존 방향이 `v2.api.creator.channel.home -> 도메인 패키지`로 유지된다. + +- [ ] **Task 4.2: 홈 API 관련 단위/통합 회귀 테스트 실행** + - Files: + - Test: `src/test/kotlin/kr/co/vividnext/sodalive/v2/api/creator/channel/home/adapter/in/web/CreatorChannelHomeControllerTest.kt` + - Test: `src/test/kotlin/kr/co/vividnext/sodalive/v2/api/creator/channel/home/application/CreatorChannelHomeFacadeTest.kt` + - Test: `src/test/kotlin/kr/co/vividnext/sodalive/v2/creator/channel/home/application/CreatorChannelHomeQueryServiceTest.kt` + - Test: `src/test/kotlin/kr/co/vividnext/sodalive/v2/creator/channel/home/domain/CreatorChannelHomeQueryPolicyTest.kt` + - Test: `src/test/kotlin/kr/co/vividnext/sodalive/v2/creator/channel/home/adapter/out/persistence/DefaultCreatorChannelHomeQueryRepositoryTest.kt` + - RED: Phase 1부터 Phase 3의 실패 확인 기록을 유지한다. + - GREEN: 아래 테스트를 모두 통과시킨다. + - 통과 확인: + - Run: `./gradlew test --tests kr.co.vividnext.sodalive.v2.api.creator.channel.home.adapter.in.web.CreatorChannelHomeControllerTest --tests kr.co.vividnext.sodalive.v2.api.creator.channel.home.application.CreatorChannelHomeFacadeTest --tests kr.co.vividnext.sodalive.v2.creator.channel.home.application.CreatorChannelHomeQueryServiceTest --tests kr.co.vividnext.sodalive.v2.creator.channel.home.domain.CreatorChannelHomeQueryPolicyTest --tests kr.co.vividnext.sodalive.v2.creator.channel.home.adapter.out.persistence.DefaultCreatorChannelHomeQueryRepositoryTest` + - Expected: PASS + - REFACTOR: 실패가 있으면 동작 변경 없이 package/import/bean wiring 문제만 수정한다. + - 기대 결과: controller, facade, service, policy, repository 회귀 테스트가 모두 통과한다. + +- [ ] **Task 4.3: ktlint와 문서 검증 기록 갱신** + - Files: + - Modify: `docs/20260617_크리에이터_채널_홈_API_구조정렬/plan-task.md` + - RED: 해당 없음. 포맷과 문서 기록 검증 task다. + - TDD 예외 사유: ktlint와 문서 기록은 구현 동작 테스트가 아니라 최종 품질 게이트다. + - 대체 검증 방법: + - Run: `./gradlew ktlintCheck` + - Expected: PASS + - Run: `./gradlew tasks --all` + - Expected: Gradle task 목록 출력 성공 + - GREEN: 검증 결과를 각 task 아래와 하단 검증 기록에 한국어로 누적 기록한다. + - REFACTOR: `git diff --name-only`로 이번 범위 밖 파일 변경이 없는지 확인한다. + - 기대 결과: 포맷 검증과 문서 유지보수 검증 결과가 기록된다. + +--- + +## 3. 전체 검증 기록 + +- 문서 생성 검증(2026-06-17): `docs/agent-guides/작업절차.md`, `docs/agent-guides/문서유지보수.md` 규칙에 따라 `docs/20260617_크리에이터_채널_홈_API_구조정렬/prd.md`와 `docs/20260617_크리에이터_채널_홈_API_구조정렬/plan-task.md`를 생성했다. +- Gradle 명령 유효성 검증(2026-06-17): sandbox 내 `./gradlew tasks --all`은 `~/.gradle` wrapper lock 파일 접근 권한 문제로 실패했다. 승인 후 동일 명령을 재실행해 `BUILD SUCCESSFUL`을 확인했다. +- 구현 검증 기록: 아직 없음. 구현 진행 시 각 task 아래에 무엇을, 왜, 어떻게 검증했는지 실행 명령과 결과를 누적한다. diff --git a/docs/20260617_크리에이터_채널_홈_API_구조정렬/prd.md b/docs/20260617_크리에이터_채널_홈_API_구조정렬/prd.md new file mode 100644 index 00000000..651de59a --- /dev/null +++ b/docs/20260617_크리에이터_채널_홈_API_구조정렬/prd.md @@ -0,0 +1,130 @@ +# PRD: 크리에이터 채널 홈 API 구조 정렬 + +## 1. Overview +기존 `GET /api/v2/creator-channels/{creatorId}/home` API의 endpoint와 응답 계약을 유지하면서, 공개 API 조립 계층을 `kr.co.vividnext.sodalive.v2.api.creator.channel.home`으로 옮기고 재사용 가능한 조회/정책/port/repository 책임을 API 패키지 밖 도메인 패키지로 정렬한다. + +--- + +## 2. Problem +- 기존 크리에이터 채널 홈 API는 controller와 response DTO가 `kr.co.vividnext.sodalive.v2.creator.channel` 하위에 있어, 현재 라이브 탭 API가 따르는 `v2.api.*` 공개 조립 계층 구조와 맞지 않는다. +- 라이브 탭 API는 `kr.co.vividnext.sodalive.v2.api.creator.channel.live`와 `kr.co.vividnext.sodalive.v2.creator.channel.live`로 공개 API와 도메인 조회 책임을 분리했지만, 홈 API는 같은 v2 공개 API 설계와 패키지 경계가 어긋나 있다. +- 공개 API DTO가 도메인 패키지 안에 남아 있으면 도메인 패키지가 API 응답 계약을 소유하는 형태가 되어 이후 탭별 API 확장 시 의존 방향이 혼동될 수 있다. +- 구조 정렬 과정에서 기존 controller를 제거하지 않고 새 controller를 추가하면 `GET /api/v2/creator-channels/{creatorId}/home` mapping 충돌이 발생할 수 있다. + +--- + +## 3. Goals +- 기존 홈 API endpoint `GET /api/v2/creator-channels/{creatorId}/home`을 유지한다. +- 기존 홈 API 응답 필드명과 필드 의미를 변경하지 않는다. +- 홈 API의 controller, facade, response DTO를 `kr.co.vividnext.sodalive.v2.api.creator.channel.home` 하위 공개 API 조립 계층으로 이동한다. +- 홈 API의 조회 service, 순수 정책, port, repository는 API 패키지 밖 도메인 패키지에 둔다. +- 도메인 패키지가 `kr.co.vividnext.sodalive.v2.api.*`에 의존하지 않도록 보장한다. +- 새 API controller 이동 시 기존 `v2.creator.channel.adapter.in.web.CreatorChannelHomeController`로 인한 Spring mapping 충돌이 없도록 기존 controller 제거 또는 이동 범위를 명확히 한다. +- 기존 홈 API controller, facade 또는 service, repository 회귀 테스트를 유지하고 새 패키지 구조에 맞게 이동한다. +- 검증 결과와 의존성 확인 결과를 `plan-task.md`에 누적 기록할 수 있게 한다. + +--- + +## 4. Non-Goals +- 홈 API 기능 추가는 하지 않는다. +- 홈 API 응답 스키마 확장, 필드명 변경, 필드 의미 변경은 하지 않는다. +- 기존 공개 endpoint path, HTTP method, 인증 정책은 변경하지 않는다. +- 라이브 탭 API(`v2.api.creator.channel.live`, `v2.creator.channel.live`) 구현은 리팩토링 대상이 아니다. +- 오디오, 시리즈, 커뮤니티, 팬 Talk, 후원 탭별 전체보기 API는 이번 범위에 포함하지 않는다. +- 불필요한 공용화, 신규 추상화, 도메인 정책 재설계는 하지 않는다. +- DB schema, 운영 DDL, 마이그레이션은 포함하지 않는다. + +--- + +## 5. Target Users +- 앱 클라이언트: 기존 홈 API 계약을 그대로 호출하는 클라이언트 +- 서버 개발자: v2 공개 API 조립 계층과 도메인 조회 계층의 의존 방향을 일관되게 유지해야 하는 개발자 +- QA/릴리즈 담당자: 리팩토링 후 기존 홈 API 동작 회귀 여부를 확인해야 하는 담당자 + +--- + +## 6. User Stories +- 앱 클라이언트는 기존과 동일하게 `GET /api/v2/creator-channels/{creatorId}/home`을 호출하고 동일한 응답 필드와 의미를 받고 싶다. +- 서버 개발자는 홈 API controller와 response DTO가 `v2.api.creator.channel.home`에 있어 공개 API 조립 계층을 쉽게 찾고 싶다. +- 서버 개발자는 도메인 조회 service와 repository가 `v2.api.*`에 의존하지 않는다는 것을 검색 명령으로 확인하고 싶다. +- 서버 개발자는 기존 홈 API controller가 남아 새 controller와 mapping 충돌을 일으키지 않는지 테스트와 검색으로 확인하고 싶다. + +--- + +## 7. Core Features + +### Feature A. 공개 API 조립 계층 이동 + +#### Requirements +- `CreatorChannelHomeController`는 `kr.co.vividnext.sodalive.v2.api.creator.channel.home.adapter.in.web` 하위로 이동한다. +- 홈 API facade는 `kr.co.vividnext.sodalive.v2.api.creator.channel.home.application` 하위에 둔다. +- `CreatorChannelHomeResponse`와 하위 response DTO는 `kr.co.vividnext.sodalive.v2.api.creator.channel.home.dto` 하위로 이동한다. +- controller는 기존 endpoint `GET /api/v2/creator-channels/{creatorId}/home`을 그대로 제공한다. +- controller는 기존과 동일하게 인증 회원을 요구하고, 비회원은 `common.error.bad_credentials` 계열 오류를 반환한다. +- facade는 공개 API 응답 DTO 조립 책임만 갖고 도메인 조회 service를 호출한다. +- 기존 `kr.co.vividnext.sodalive.v2.creator.channel.adapter.in.web.CreatorChannelHomeController`는 남기지 않는다. + +#### Edge Cases +- 새 controller와 기존 controller가 동시에 bean으로 등록되어 같은 path mapping을 제공하면 안 된다. + +### Feature B. 도메인 조회 계층 정렬 + +#### Requirements +- 홈 API 조회 service, 순수 정책, domain model, port, repository는 `kr.co.vividnext.sodalive.v2.creator.channel.home` 하위로 정렬한다. +- 도메인 조회 계층은 API response DTO를 import하지 않는다. +- 도메인 조회 계층은 API facade나 controller를 import하지 않는다. +- 의존 방향은 항상 `v2.api.creator.channel.home -> v2.creator.channel.home`이다. +- repository는 기존 QueryDSL 조회 의미와 정책을 변경하지 않는다. + +#### Edge Cases +- 라이브 탭 API의 `v2.api.creator.channel.live`, `v2.creator.channel.live` 패키지는 이번 구조 정렬 대상이 아니므로 동작 변경 없이 import 영향만 확인한다. + +### Feature C. 공개 계약 보존 회귀 검증 + +#### Requirements +- 홈 API 최상위 응답 필드는 기존과 동일하게 유지한다. + - `creator` + - `currentLive` + - `latestAudioContent` + - `channelDonations` + - `notices` + - `schedules` + - `audioContents` + - `series` + - `communities` + - `fanTalk` + - `introduce` + - `activity` + - `sns` +- 기존 하위 DTO 필드명과 의미를 변경하지 않는다. +- controller 테스트는 기존 endpoint와 대표 JSON field path를 검증한다. +- facade 또는 service 테스트는 도메인 조회 결과가 기존 응답 DTO로 변환되는 흐름을 검증한다. +- repository 테스트는 기존 조회 정책 회귀를 유지한다. +- `./gradlew ktlintCheck`를 실행하고 결과를 계획 문서에 기록한다. + +#### Edge Cases +- response DTO 패키지 이동으로 Jackson `@JsonProperty`가 누락되어 `is*` 필드명이 바뀌면 안 된다. + +--- + +## 8. Technical Constraints +- 언어/런타임은 Kotlin + Java 17을 유지한다. +- 빌드와 검증은 Gradle Wrapper(`./gradlew`)를 사용한다. +- Spring Boot 2.7.14, JUnit 5, MockMvc, QueryDSL 기존 관례를 따른다. +- 패키지 구조는 `docs/agent-guides/코드스타일.md`의 공개 API 조립 계층과 도메인 패키지 의존 방향 규칙을 따른다. +- 테스트는 `docs/agent-guides/테스트스타일.md`의 RED, GREEN, REFACTOR 절차를 따른다. +- 문서와 검증 기록은 `docs/agent-guides/작업절차.md`, `docs/agent-guides/문서유지보수.md`를 따른다. + +--- + +## 9. Metrics +- 홈 API endpoint와 응답 계약 회귀 테스트 통과 여부 +- facade 또는 service 단위 테스트 통과 여부 +- repository 단위 테스트 통과 여부 +- `./gradlew ktlintCheck` 통과 여부 +- 도메인 패키지의 `v2.api.*` import 검색 결과 0건 여부 + +--- + +## 10. Open Questions +- 없음. 이번 범위는 동작 보존 리팩토링이며, 응답 계약이나 기능 정책 변경은 포함하지 않는다.