21 KiB
21 KiB
PRD: V2 공통 접근 가드와 도메인 액션
1. Overview
v2 화면에 분산된 로그인·성인 콘텐츠 접근 검사와 반복 사용자 동작을 단일 소유 함수로 통합하고, 각 화면은 화면별 조회 결과를 표시한 뒤 공통 기능을 조합해 사용하도록 정리한다.
2. Problem
현재 v2는 동일한 접근 조건과 화면 이동 동작을 여러 화면에서 각각 구현하고 있다.
- 로그인, 국내 사용자 본인인증, 성인 콘텐츠 표시 설정 검사가
MainV2LoginGuard,CreatorChannelActivity,HomeOnAirLiveActivity에 중복되어 있다. - 일부 화면은
SharedPreferenceManager.token을 직접 확인하고, 일부 화면은 별도ensure...함수를 사용해 동일 정책의 변경 지점이 분산되어 있다. - 오디오 콘텐츠, 시리즈, 크리에이터 채널, 커뮤니티 게시글, 채팅방 등의 진입에서 ID 검사, 접근 검사,
Intent생성이 화면마다 반복된다. - Home이
CreatorChannelLiveCoordinator를 직접 사용하는 등 공통 기능이 실제 소유 도메인이 아닌 최초 구현 화면 패키지에 위치한다. - 동일 기능의 새 진입 화면이 추가되면 기존 구현을 재사용하기보다 조건과 이동 코드를 다시 작성할 가능성이 있다.
- 호출부가 접근 가드 적용을 기억해야 하므로 진입 경로에 따라 보호 정책이 누락될 수 있다.
3. Goals
- 로그인 및 성인 콘텐츠 접근 판단을
v2공통 Access 구현 한 곳에서 소유한다. - Activity와 Fragment가 동일한 Access 진입점을 사용한다.
- 기존 로그인 화면, 본인인증, 성인 콘텐츠 설정 이동 동작과 back stack/extra 계약을 유지한다.
- 둘 이상의
v2화면에서 반복되는 사용자 동작은 해당 도메인의 단일 Action 진입점으로 통합한다. - 도메인 Action은 화면 전용 UI model 대신 안정적인 ID, 값 객체 또는 명시적인 command를 입력받는다.
- 도메인 Action의 결과가 호출 화면에 따라 달리 표현되어야 하면 명시적인 result 계약으로 반환한다.
- Home, Content, Creator Channel, On-air 등 feature는 화면별 최초 집계 API와 UI 구성을 유지하면서 공통 Action을 조합한다.
- 새 화면은 기존 도메인 Action을 호출해 동일 정책과 동작을 재구현하지 않고 사용할 수 있어야 한다.
- 각 Phase는 기존 동작을 보존하고 독립적으로 테스트·컴파일 가능한 상태로 완료한다.
- 하위 Activity가 반환하는 도메인 변경은
ActivityResult를 명시적 결과로 변환한 뒤 feature의 단일 handler에서 후처리한다.
4. Non-Goals
v2전체 파일을 한 번에 이동하거나 일괄 패키지 재구성하지 않는다.- Gradle 멀티 모듈 전환은 포함하지 않는다.
- 서버 API, 공개 DTO 스키마, 데이터베이스 계약은 변경하지 않는다.
- Home 추천, Creator Channel 홈과 같은 화면별 집계 API를 여러 개의 도메인 API 호출로 강제 분해하지 않는다.
- 한 번만 사용되고 별도 정책이 없는 화면 전용 동작에 형식적인 Action, UseCase, Repository 인터페이스를 추가하지 않는다.
- UI 레이아웃, 문구, 디자인, 화면 전환 UX를 변경하지 않는다.
- 본인인증 SDK, 로그인 API, 콘텐츠 구매, 라이브 결제 등 레거시 기능 자체를 수정하지 않는다.
- 레거시 파일을 공통화를 위해 직접 수정하지 않는다. 필요한 기능은
v2wrapper/adapter에서 호출한다. - FanTalk, Donation, Schedule 등 현재 재사용 수요가 확인되지 않은 기능을 추측으로 공통화하지 않는다.
- Creator Channel 내부의 Community 변경 전파를 위해 전역 EventBus 또는 application singleton observer를 도입하지 않는다.
- 직접적인 요청자와 결과 처리자가 명확한 흐름을 일괄적으로
SharedFlow또는 observer 기반으로 변경하지 않는다.
5. Target Users
- Home, Content, Creator Channel, On-air, Chat 등
v2화면을 이용하는 사용자 - 동일 기능을 새로운
v2화면에 연결해야 하는 Android 개발자 - 로그인·성인 접근·콘텐츠 진입 정책을 변경하거나 검증하는 유지보수 담당자
6. User Stories
- 비로그인 사용자는 어느
v2화면에서 보호 기능을 선택하더라도 동일한 로그인 안내를 받고 싶다. - 성인 콘텐츠 접근 조건을 충족하지 않은 사용자는 진입 화면과 무관하게 동일한 인증 또는 설정 안내를 받고 싶다.
- 로그인 및 접근 조건을 충족한 사용자는 기존과 동일하게 콘텐츠, 라이브, 커뮤니티, 채팅 기능으로 이동하고 싶다.
- 개발자는 라이브 입장, 오디오 콘텐츠 열기, 커뮤니티 게시글 열기와 같은 동작을 한 번 구현한 뒤 여러 화면에서 호출하고 싶다.
- 개발자는 공통 정책 변경 시 모든 Activity와 Fragment를 검색해 개별 조건문을 수정하지 않고 싶다.
- 개발자는 화면별 집계 API와 UI 구성을 유지하면서 도메인별 공통 동작만 조합하고 싶다.
7. Core Features
Feature A: V2 공통 Access 계약
로그인과 성인 콘텐츠 접근 조건을 화면과 분리된 공통 계약으로 정의한다.
Requirements
- Access 요구사항은 최소
Login,AdultContent를 명시적으로 표현한다. AdultContent는 로그인 확인 후 국가/본인인증, 성인 콘텐츠 표시 설정 순서로 평가한다.- 로그인 여부는 기존과 동일하게
SharedPreferenceManager.token.isBlank()기준을 사용한다. countryCode.ifBlank { "KR" } == "KR"이면 국내 사용자로 판단한다.- 국내 사용자이고
SharedPreferenceManager.isAuth == false이면 본인인증 필요 결과를 반환한다. SharedPreferenceManager.isAdultContentVisible == false이면 성인 콘텐츠 설정 필요 결과를 반환한다.- Access 판단 결과는 최소
Allowed,LoginRequired,AdultVerificationRequired,AdultContentSettingRequired를 구분한다. - 정책 판단 로직은 Activity, Fragment, Dialog,
Intent에 의존하지 않는 단위 테스트 가능한 형태로 작성한다. - 세션 값 접근은 주입 가능한 snapshot/provider 경계를 두어 테스트에서 전역 상태에 직접 의존하지 않게 한다.
Edge Cases
- blank 국가 코드는 기존 정책과 동일하게
KR로 처리한다. - 국외 사용자는 국내 본인인증 조건을 건너뛰고 성인 콘텐츠 표시 설정을 확인한다.
- 로그인만 필요한 동작은 성인 인증과 콘텐츠 표시 설정을 검사하지 않는다.
- invalid ID 또는 지원하지 않는 route는 기존처럼 Access 실행 전에 종료할 수 있다.
Feature B: V2 공통 Access 실행기
Access 판단 결과를 기존 로그인, 본인인증, 설정 이동 UX에 연결하는 재사용 진입점을 제공한다.
Requirements
- Activity와 Fragment가 동일한
V2AccessGuard공개 진입점을 사용할 수 있어야 한다. - 허용된 경우에만 전달된 동작을 정확히 한 번 실행한다.
- 미로그인인 경우 기존
LoginActivity진입 계약과 호출 화면의 extra/back stack 동작을 유지한다. - 본인인증이 필요한 경우 기존
V2ModalDialog,Auth.auth,MyPageViewModel.authVerify흐름을 재사용한다. - 성인 콘텐츠 표시 설정이 필요한 경우
ContentSettingsActivity에Constants.EXTRA_SHOW_SENSITIVE_CONTENT_GUIDE = true를 전달한다. MainV2LoginGuard,CreatorChannelActivity.ensureLoginAndAdultAuth,HomeOnAirLiveActivity.ensureLoginAndAdultAuth의 중복 판단 구현을 제거한다.v2UI의 직접 로그인 검사 중 화면 진입 자체를 막는 검사와 개별 행동을 막는 검사를 구분해 기존 종료 여부를 유지한다.
Edge Cases
- 인증 callback이 Activity 종료 이후 전달되는 경우 안전하게 UI 후처리를 생략한다.
- 로그인 화면 이동 시 호출 Activity가 보존하던 기존
intent.extras전달 계약을 유지한다. - 화면 진입 guard와 클릭 행동 guard가 같은 Access 정책을 사용하더라도 화면 종료 여부는 호출 feature의 기존 UX를 유지한다.
Feature C: 도메인 Action 카탈로그와 단일 소유권
반복 동작의 호출부, 현재 차이, 소유 도메인, 입력·출력 계약을 구현 전에 확정한다.
Requirements
- 각 Action 후보는 최소 두 개의 실제 호출부가 있거나 여러 화면에서 반드시 동일해야 하는 정책을 포함해야 한다.
- Action별로 소유 도메인, 호출 화면, 입력, 결과, UI side effect, 레거시 의존성을 기록한다.
- 화면 전용 집계 query와 도메인 공통 action을 구분한다.
- Action의 공개 입력에
HomeRecommendation...UiModel,CreatorChannel...Response같은 feature 전용 타입을 사용하지 않는다. - 동일한 이름이더라도 정책이 다른 동작은 Access와 도메인 정책으로 나누고 하나의 범용 함수에 합치지 않는다.
Initial Action Catalog
| 소유자 | 단일 동작 후보 | 주요 호출 화면 | 공통 계약 방향 |
|---|---|---|---|
| Access | 로그인/성인 콘텐츠 접근 확인 | Home, Content, Creator Channel, On-air, Chat | AccessRequirement -> AccessDecision |
| Content | 오디오 콘텐츠 상세 진입 | Home, Content, Content Overview, Creator Channel | contentId, 접근 정보 -> 진입/차단 결과 |
| Content | 시리즈 상세 진입 | Content, Creator Channel | seriesId, 접근 정보 -> 진입/차단 결과 |
| Live | 라이브 상세/입장 | Home, Creator Channel, On-air | liveId -> 상세/입장/비밀번호/결제/차단 결과 |
| Creator | 크리에이터 채널 진입 | Home, Content, Main, AI 캐릭터 route | creatorId -> 진입/무시 결과 |
| Community | 커뮤니티 게시글 진입 | Home, Creator Channel | postId -> 진입/차단 결과 |
| Community | 게시글 작성/수정/삭제/고정 변경 결과 | Creator Channel Home/Community projection | mutation -> CommunityChange |
| Chat | DM/채팅방 진입 | Home, Chat, Creator Channel | room/creator 식별자 -> 생성/진입/차단 결과 |
Result Propagation Policy
- 하위 Activity의 완료 결과로 도메인 변경을 전달하는 기존 흐름은
ActivityResultLauncher생명주기 계약을 유지한다. - raw
RESULT_OK, nullable extra, 화면별 boolean은v2adapter에서 명시적인 도메인 Action 결과 또는 change 타입으로 변환한다. - feature는 변환된 결과를
handleContentChange,handleLiveChange,handleCommunityChange,handleChatChange와 같은 의미 있는 단일 handler에 전달한다. - 단일 handler는 현재 feature가 조합하는 projection 갱신, Activity result 재전달, navigation, 화면별 후처리를 결정한다.
- 도메인 Action은
refreshHome,refreshTab, Fragment 참조처럼 호출 화면의 구성을 나타내는 callback을 입력으로 받지 않는다. - Repository 또는 Action이 현재 화면 안에서 직접 반환하는 비동기 결과는 불필요하게
ActivityResult로 변환하지 않고 같은 단일 handler에 전달한다. - 발행자와 처리자가 직접 연결되고 갱신 대상이 한 feature 내부에 있으면 명시적인 결과 전달을 기본값으로 사용한다.
- Activity 밖의 서로 독립적인 소비자가 여러 개 생기거나 백그라운드 변경을 함께 관찰해야 할 때만 Activity 범위
SharedFlow, application event 또는 Repository Flow를 검토한다. - 전역 EventBus/singleton observer는 이벤트 범위, creator/content 식별자, replay, 중복 처리, lifecycle 요구사항이 먼저 확정되지 않으면 도입하지 않는다.
Feature D: Content 공통 Action
오디오 콘텐츠와 시리즈 상세 진입의 공통 사전 조건과 navigation 계약을 Content가 한 번만 구현한다.
Requirements
- 오디오 콘텐츠 ID와 시리즈 ID 유효성 검사를 한 곳에서 소유한다.
- 성인 여부를 호출부에서 알 수 있는 경우 공통 Access를 적용한다.
- 성인 여부를 알 수 없는 기존 경로는 임의 판정하지 않고 기존 정책 또는 상세 화면 정책을 유지한다.
- 기존
Constants.EXTRA_AUDIO_CONTENT_ID,Constants.EXTRA_SERIES_ID계약을 유지한다. - Home, Content, Content Overview, Creator Channel의 해당 진입점은 Content Action을 사용한다.
- Action 적용 후 대상 UI에서
AudioContentDetailActivity,SeriesDetailActivityIntent를 직접 구성하지 않는다.
Edge Cases
- ID가 0 이하이면 기존처럼 이동하지 않는다.
- 접근 조건이 충족되지 않으면 상세 Activity를 시작하지 않는다.
- 화면별 분석 source 또는 후처리가 존재하면 command metadata 또는 호출 결과 처리로 보존하고 도메인 규칙과 혼합하지 않는다.
Feature E: Live 공통 Action
현재 Creator Channel과 Home에서 공유하는 Coordinator 및 On-air 중복 흐름을 Live 소유의 단일 진입점으로 정리한다.
Requirements
CreatorChannelLiveCoordinator의 실제 소유권을 Live로 이동하거나 Live 공개 진입점 뒤의 호환 adapter로 전환한다.- 라이브 상세 조회, 입장 가능 여부, 무료/결제, 비밀번호, 상세 표시 분기를 한 번만 구현한다.
- 기존 오디오 서비스 중단, 라이브 입장 API, 예약/결제/비밀번호 Dialog,
LiveRoomActivity이동 동작을 보존한다. - Home, Creator Channel, On-air가 동일한 Live 진입점을 사용한다.
- Home 새로고침과 같은 호출 화면 전용 후처리는 Live 규칙과 분리된 callback/result 처리로 유지한다.
- 라이브 생성처럼 하위 Activity가 결과를 반환하는 흐름은 raw
ActivityResult를 명시적 Live 결과로 변환하고 Creator Channel의 단일 Live result handler에서 입장 또는 안내 후처리를 결정한다. - 레거시
LiveViewModel, Dialog, Activity는 수정하지 않고 adapter로 사용한다.
Edge Cases
- 방 관리자 본인 입장, 무료/결제 완료, 비밀번호 방, 유료 방 조건의 기존 분기 순서를 유지한다.
- 라이브 상세에 channel 정보가 없는 경우 기존처럼 상세 화면 흐름을 사용한다.
- 입장 성공 이후 호출 화면 새로고침 여부가 달라도 입장 정책은 중복 구현하지 않는다.
Feature F: Creator, Community, Chat 공통 Action
반복 navigation과 접근 조건을 각 소유 도메인의 단일 진입점으로 통합한다.
Requirements
- Creator Action은 유효한
creatorId를 기준으로 Creator Channel 진입을 제공한다. - Community Action은 로그인과 유효한
postId확인 후 게시글 상세 진입을 제공한다. - Community mutation 성공은
CommunityChange.Created,Updated,Deleted,PinChanged처럼 발생한 사실을 나타내는 명시적 결과로 표현한다. - 레거시 Community 작성/수정 Activity의
ActivityResult.RESULT_OK는v2adapter에서CommunityChange로 변환한다. - Community Action과 mutation 결과는
refreshHome,refreshCommunityTab처럼 호출 화면 구조를 나타내는 callback을 입력으로 받지 않는다. - Creator Channel은
handleCommunityChange단일 composition 진입점에서 Community 변경 종류에 따른 Home/Community projection 갱신을 결정한다. - Community 작성, 수정, 삭제, 고정 변경의 성공 경로는 직접 개별 refresh를 호출하지 않고
handleCommunityChange를 사용한다. - Chat Action은 room 기반 진입과 creator 기반 DM 생성/진입의 차이를 명시적인 command로 구분한다.
- 기존 owner/non-owner, AI/DM 채팅 타입 분기와 Activity result 계약을 유지한다.
- Chat/DM 하위 Activity 결과에 따라 호출 화면 후처리가 필요한 경우 raw result를 명시적 Chat 결과로 변환하고 호출 feature의 단일 handler에서 처리한다.
- Home의 UI model과 Creator Channel의 Response를 Action 공개 입력으로 사용하지 않는다.
- 실제 중복이 확인되지 않은 follow, 알림, FanTalk, Donation mutation은 이번 Action에 추측으로 포함하지 않는다.
Edge Cases
- creator, post, room ID가 유효하지 않으면 기존처럼 이동하지 않는다.
- Activity result가 필요한 호출부는 일반
startActivity로 단순화하지 않는다. - 채팅방 생성 API가 필요한 경로와 기존 room ID로 바로 진입하는 경로를 혼합하지 않는다.
- 아직 생성되지 않은 Creator Channel Fragment는 자체 initial load로 최신 데이터를 조회하며, 현재 Activity 내부 두 projection 갱신을 위해 이벤트 replay/state 동기화 계층을 추가하지 않는다.
Feature G: 의존 방향과 호환성 정리
공통 Action 도입 후 feature 간 직접 의존과 이전 중복 진입점을 정리한다.
Requirements
- feature는 공통 Access 또는 소유 도메인 Action을 호출할 수 있다.
- 도메인 Action은 Home, Content Main, Creator Channel 같은 호출 feature를 import하지 않는다.
- data 구현은 Retrofit DTO와 레거시 API를 알고, 도메인 정책은 Retrofit 및 Android UI를 알지 않도록 유지한다.
- Android UI가 필요한 navigation/Dialog wrapper는 정책과 분리된 application/presentation action으로 둔다.
- 직접적인 Activity 결과 흐름은 domain/application event로 우회하지 않고
ActivityResult -> 명시적 결과 -> feature handler의존 방향을 유지한다. AppDI.kt는 새 계약과 구현을 조립하되 레거시 등록을 불필요하게 변경하지 않는다.- 모든 호출부 전환이 끝난 이전 helper와 중복 함수만 제거한다.
- package 이동은 Action 전환 후 필요성이 확인된 파일에 한해 별도 Task로 수행한다.
8. UX / UI Expectations
- 로그인, 본인인증, 성인 콘텐츠 설정 안내의 표시 순서와 문구를 유지한다.
- 허용된 사용자의 콘텐츠, 라이브, 커뮤니티, 채팅 진입 결과는 기존과 같아야 한다.
- 기존 Dialog 크기, Activity flag, Intent extra, Activity result 처리와 화면 새로고침 동작을 유지한다.
- 리팩토링 자체로 신규 화면, 버튼, Toast, loading UI를 추가하지 않는다.
9. Technical Constraints
- 변경 범위는
app/src/main/java/kr/co/vividnext/sodalive/v2, 대응app/src/test/.../v2,AppDI.kt, 본 작업 문서로 제한한다. - 레거시 파일은 직접 수정하지 않고 기존 기능을 호출하는
v2wrapper/adapter를 작성한다. - API -> Repository -> ViewModel -> Activity/Fragment의 기존 흐름을 임의로 깨지 않는다.
- 신규 테스트는 Access 판단, Action 입력·출력, route/정책 같은 순수 로직을 우선 검증한다.
- 소스 문자열 테스트만으로 정책을 검증하지 않고, 가능한 범위에서 실제 입력·출력 단위 테스트를 추가한다.
- Community 변경처럼 호출 화면이 갱신 대상을 결정하는 경우 도메인 결과와 feature invalidation handler를 분리하고, observer는 Activity 밖의 다수 소비자에게 전파할 요구가 생길 때만 도입한다.
- 각 도메인 Phase에서 Activity result, callback, refresh 후처리를 조사하고 단일 handler가 필요한 흐름만 최소 범위로 통합한다.
- 기존 source test가 이전 함수명을 고정한 경우 새 공개 계약을 검증하도록 최소 수정한다.
- 각 Phase는 관련 단위 테스트,
:app:compileDebugKotlin,:app:ktlintCheck,git diff --check를 통과해야 한다. - 테스트 클래스가 추가되면
docs/agent-guides/build-test-style.md에 단일 실행 예시를 추가한다. - 작업 범위가 변경되면 구현 전에
plan-task.md를 먼저 갱신한다.
10. Metrics
- 로그인과 성인 접근 정책을 독립적으로 판단하는 구현이
v2내 한 곳에만 존재한다. CreatorChannelActivity와HomeOnAirLiveActivity에 화면 전용ensureLoginAndAdultAuth구현이 남지 않는다.- 대상 UI 호출부에서
SharedPreferenceManager.token.isBlank()로 개별 행동 접근을 판단하지 않는다. - 오디오, 시리즈, 라이브, 크리에이터, 커뮤니티, 채팅의 합의된 호출부가 각각 단일 Action 진입점을 사용한다.
- Creator Channel의 Community 작성, 수정, 삭제, 고정 변경 성공 경로가 명시적
CommunityChange를 거쳐 하나의 composition handler에서 projection을 갱신한다. - 공통 Action 공개 계약이 feature 전용 UI model 또는 DTO에 의존하지 않는다.
- 기존 Intent extra, Activity result, 로그인/인증/설정 UX를 검증하는 회귀 테스트가 통과한다.
- 각 Phase 완료 시 중복 제거 전후 호출부 목록과 검증 결과가
plan-task.md에 누적 기록된다.
11. Open Questions
- 없음. 전체 작업은 한 번에 패키지를 재구성하지 않고 Access부터 도메인별로 순차 진행한다.
- 각 도메인 Phase에서 조사 결과 실제 중복이 없거나 화면별 정책이 다른 동작은 공통 Action으로 만들지 않고 계획 문서에 근거를 기록한다.