refactor(v2): 도메인 액션 경로를 통합한다
This commit is contained in:
@@ -26,6 +26,8 @@
|
||||
- 도메인 Action의 결과가 호출 화면에 따라 달리 표현되어야 하면 명시적인 result 계약으로 반환한다.
|
||||
- Home, Content, Creator Channel, On-air 등 feature는 화면별 최초 집계 API와 UI 구성을 유지하면서 공통 Action을 조합한다.
|
||||
- 새 화면은 기존 도메인 Action을 호출해 동일 정책과 동작을 재구현하지 않고 사용할 수 있어야 한다.
|
||||
- 푸시와 딥링크로 유입된 도메인 이동도 일반 화면과 같은 Action 및 Live 진입 정책을 사용한다.
|
||||
- 푸시와 딥링크로 Live 상태를 조회해 목적지를 판단하는 동안 사용자에게 진행 상태와 실패 결과를 표시한다.
|
||||
- 각 Phase는 기존 동작을 보존하고 독립적으로 테스트·컴파일 가능한 상태로 완료한다.
|
||||
- 하위 Activity가 반환하는 도메인 변경은 `ActivityResult`를 명시적 결과로 변환한 뒤 feature의 단일 handler에서 후처리한다.
|
||||
|
||||
@@ -39,7 +41,7 @@
|
||||
- 한 번만 사용되고 별도 정책이 없는 화면 전용 동작에 형식적인 Action, UseCase, Repository 인터페이스를 추가하지 않는다.
|
||||
- UI 레이아웃, 문구, 디자인, 화면 전환 UX를 변경하지 않는다.
|
||||
- 본인인증 SDK, 로그인 API, 콘텐츠 구매, 라이브 결제 등 레거시 기능 자체를 수정하지 않는다.
|
||||
- 레거시 파일을 공통화를 위해 직접 수정하지 않는다. 필요한 기능은 `v2` wrapper/adapter에서 호출한다.
|
||||
- 레거시 파일을 공통화를 위해 직접 수정하지 않는다. 단, V2 푸시/딥링크 진입점으로 계속 사용하는 `DeepLinkActivity`는 사용자 승인에 따라 기존 payload 계약을 Action에 연결하는 최소 변경을 허용한다.
|
||||
- FanTalk, Donation, Schedule 등 현재 재사용 수요가 확인되지 않은 기능을 추측으로 공통화하지 않는다.
|
||||
- Creator Channel 내부의 Community 변경 전파를 위해 전역 EventBus 또는 application singleton observer를 도입하지 않는다.
|
||||
- 직접적인 요청자와 결과 처리자가 명확한 흐름을 일괄적으로 `SharedFlow` 또는 observer 기반으로 변경하지 않는다.
|
||||
@@ -112,6 +114,16 @@ Access 판단 결과를 기존 로그인, 본인인증, 설정 이동 UX에 연
|
||||
- Action의 공개 입력에 `HomeRecommendation...UiModel`, `CreatorChannel...Response` 같은 feature 전용 타입을 사용하지 않는다.
|
||||
- 동일한 이름이더라도 정책이 다른 동작은 Access와 도메인 정책으로 나누고 하나의 범용 함수에 합치지 않는다.
|
||||
|
||||
#### Final Ownership Boundary
|
||||
- Access는 로그인 여부, 본인인증, 성인 콘텐츠 설정 등 접근 허용 판단과 기존 로그인/인증/설정 UX 실행을 소유한다.
|
||||
- 도메인 Action은 안정적인 ID/command 유효성 검사, 동작에 필요한 `AccessRequirement` 선택, Access 실행 요청과 `Ignored`/`Blocked`/도메인 결과 반환을 소유한다.
|
||||
- Action Handler는 Access 실행기 주입과 레거시 `Intent`/Activity/Dialog/navigation adapter를 소유하고, 호출 feature의 UI model이나 화면별 refresh를 알지 않는다.
|
||||
- feature는 UI model을 command로 변환하고, 화면별 최초 query/API, 단일 화면만 소유하는 생성·mutation API, `ActivityResult` 수신, refresh/callback/projection 조합을 소유한다.
|
||||
- 보호된 도메인 Action을 호출하는 화면은 같은 Access guard를 바깥에서 중복 실행하지 않는다. 공통 Access 직접 호출은 화면 진입 자체 또는 화면 전용 동작의 접근 제한에 사용한다.
|
||||
- 도메인과 관련된 모든 코드를 이관하지 않는다. 둘 이상의 호출부에서 반복되거나 모든 진입점에서 같아야 하는 유효성·접근 정책·결과 계약만 도메인 Action으로 이관한다.
|
||||
- 도메인 Action의 범위는 페이지 이동만이 아니다. 반복되는 사전 조건과 결과 계약은 Action이, 실제 Android 화면 이동은 Handler가 소유한다.
|
||||
- parent-child Fragment composition, same-feature child flow, 도메인 Action이 없는 system-only route, Home on-air 목록 화면 진입, 단일 owner 기능은 합의된 반복 정책이 없으면 feature에 유지한다.
|
||||
|
||||
#### Initial Action Catalog
|
||||
| 소유자 | 단일 동작 후보 | 주요 호출 화면 | 공통 계약 방향 |
|
||||
|---|---|---|---|
|
||||
@@ -119,10 +131,10 @@ Access 판단 결과를 기존 로그인, 본인인증, 설정 이동 UX에 연
|
||||
| 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` -> 진입/차단 결과 |
|
||||
| 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 식별자 -> 생성/진입/차단 결과 |
|
||||
| Chat | DM/채팅방 진입 | Home, Chat, Creator Channel | room/creator 식별자 -> 진입/차단/무시 결과 |
|
||||
|
||||
#### Result Propagation Policy
|
||||
- 하위 Activity의 완료 결과로 도메인 변경을 전달하는 기존 흐름은 `ActivityResultLauncher` 생명주기 계약을 유지한다.
|
||||
@@ -176,14 +188,16 @@ Access 판단 결과를 기존 로그인, 본인인증, 설정 이동 UX에 연
|
||||
반복 navigation과 접근 조건을 각 소유 도메인의 단일 진입점으로 통합한다.
|
||||
|
||||
#### Requirements
|
||||
- Creator Action은 유효한 `creatorId`를 기준으로 Creator Channel 진입을 제공한다.
|
||||
- Creator Action은 유효한 `creatorId`와 `AccessRequirement.Login`을 기준으로 Creator Channel 진입 또는 명시적 차단/무시 결과를 제공한다.
|
||||
- Community Action은 로그인과 유효한 `postId` 확인 후 게시글 상세 진입을 제공한다.
|
||||
- Community mutation 성공은 `CommunityChange.Created`, `Updated`, `Deleted`, `PinChanged`처럼 발생한 사실을 나타내는 명시적 결과로 표현한다.
|
||||
- 레거시 Community 작성/수정 Activity의 `ActivityResult.RESULT_OK`는 `v2` adapter에서 `CommunityChange`로 변환한다.
|
||||
- Community Action과 mutation 결과는 `refreshHome`, `refreshCommunityTab`처럼 호출 화면 구조를 나타내는 callback을 입력으로 받지 않는다.
|
||||
- Creator Channel은 `handleCommunityChange` 단일 composition 진입점에서 Community 변경 종류에 따른 Home/Community projection 갱신을 결정한다.
|
||||
- Community 작성, 수정, 삭제, 고정 변경의 성공 경로는 직접 개별 refresh를 호출하지 않고 `handleCommunityChange`를 사용한다.
|
||||
- Chat Action은 room 기반 진입과 creator 기반 DM 생성/진입의 차이를 명시적인 command로 구분한다.
|
||||
- Chat Action은 room 기반 진입과 creator 기반 DM/owner 목록 진입의 차이를 명시적인 command로 구분하고, 유효 ID 확인 후 `AccessRequirement.Login`을 적용한다.
|
||||
- Creator Channel AI Chat의 `AccessRequirement.AdultContent` 사전 조건과 `createChatRoom(characterId)` API는 feature가 유지한다. 생성 성공 후 받은 room ID의 로그인 정책과 화면 이동은 Chat Action에 위임한다.
|
||||
- Creator, Community, Chat의 보호된 Action 호출부는 같은 로그인 guard를 화면에서 중복 실행하지 않는다.
|
||||
- 기존 owner/non-owner, AI/DM 채팅 타입 분기와 Activity result 계약을 유지한다.
|
||||
- Chat/DM 하위 Activity 결과에 따라 호출 화면 후처리가 필요한 경우 raw result를 명시적 Chat 결과로 변환하고 호출 feature의 단일 handler에서 처리한다.
|
||||
- Home의 UI model과 Creator Channel의 Response를 Action 공개 입력으로 사용하지 않는다.
|
||||
@@ -199,28 +213,63 @@ Access 판단 결과를 기존 로그인, 본인인증, 설정 이동 UX에 연
|
||||
공통 Action 도입 후 feature 간 직접 의존과 이전 중복 진입점을 정리한다.
|
||||
|
||||
#### Requirements
|
||||
- feature는 공통 Access 또는 소유 도메인 Action을 호출할 수 있다.
|
||||
- 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`는 새 계약과 구현을 조립하되 레거시 등록을 불필요하게 변경하지 않는다.
|
||||
- `AppDI.kt`는 실제 외부 의존성, 공유 생명주기 또는 구현 교체가 필요한 계약만 조립한다. 상태 없는 Action/Handler는 호출 경계에서 직접 조합할 수 있으며 DI 등록 자체를 완료 조건으로 삼지 않는다.
|
||||
- 모든 호출부 전환이 끝난 이전 helper와 중복 함수만 제거한다.
|
||||
- package 이동은 Action 전환 후 필요성이 확인된 파일에 한해 별도 Task로 수행한다.
|
||||
- feature 간 직접 Activity/Coordinator 의존은 합의된 반복 navigation/정책 대상만 Action/Handler로 대체한다. parent-child composition, same-feature child flow, system/deeplink/notification route, 목록 화면 진입과 단일 owner 기능은 예외 근거를 기록하고 유지할 수 있다.
|
||||
|
||||
### Feature H: 푸시/딥링크의 도메인 Action 연결
|
||||
푸시와 딥링크 payload 해석은 기존 진입점에 유지하고, 해석된 도메인 command의 실행은 이미 분리된 Action과 Live 진입 정책에 위임한다.
|
||||
|
||||
#### Requirements
|
||||
- FCM 알림과 앱 내 알림 목록은 기존처럼 `DeepLinkActivity`로 진입하며, 별도의 범용 `PushRouteAction`이나 EventBus를 추가하지 않는다.
|
||||
- `DeepLinkActivity`가 foreground에서 직접 처리하는 오디오, 시리즈, 크리에이터, 커뮤니티 게시글, DM 이동은 각각 Content, Creator, Community, Chat Action을 사용한다.
|
||||
- `DeepLinkActivity`에서 Main으로 전달한 payload와 앱 cold start payload는 `MainV2Activity`가 같은 도메인 Action으로 처리한다.
|
||||
- 채팅 deep-link 값과 유효한 room ID가 함께 있으면 DM Action을 먼저 실행하고, 그 외 유효한 room ID는 channel/content ID보다 먼저 Live 진입으로 처리한다.
|
||||
- Live 진입은 `LiveActionCoordinator.enterLiveRoom(roomId)`를 사용한다. 조회 결과 현재 진행 중인 라이브이면 기존 무료/유료/비밀번호 정책에 따라 입장하고, 예약 또는 즉시 입장할 channel 정보가 없으면 레거시 Live Detail 화면을 표시한다.
|
||||
- Community payload에 유효한 `postId`가 있으면 creator ID보다 우선하여 `CommunityActionCommand.PostDetail(postId)`를 실행한다.
|
||||
- Community payload의 `postId`는 서버 실제 사용상 항상 전달되는 값으로 보되 선택적 계약은 유지한다. 유효한 `postId`가 없고 creator ID가 있으면 `CreatorActionCommand.Profile(creatorId)`로 Creator Channel을 표시한다.
|
||||
- Community 딥링크의 `deep_link_sub5`와 `${URISCHEME}://community/{id}` path ID는 기존 계약의 `creatorId`다. `postId`는 query/canonical extra로 별도 정규화하므로, `routeByDeepLinkValue("community")`는 post ID가 없는 레거시 Creator fallback으로 유지한다.
|
||||
- 현재 DM 푸시 계약은 `${URISCHEME}://chat/{roomId}`이며 Chat Action의 `DmRoom`으로 처리한다. 기존 `message`/`message_id`는 DM room ID로 재해석하지 않고 과거 알림 수신 호환용 legacy Message route로만 유지한다.
|
||||
- audio detail notification은 Content Action을 사용하고, 도메인 상세 이동이 아닌 audio player 화면 route는 기존 Access/system route를 유지한다.
|
||||
- 현재 서버에서 더 이상 발행하지 않는 legacy message, audition, payment callback처럼 대응 도메인 Action이 없는 system-only route는 기존 직접 처리를 유지한다.
|
||||
- `LiveRoomActivity`가 foreground일 때 broadcast로 payload를 전달하는 기존 특수 경로와 `Intent` flag/extra 계약을 유지한다.
|
||||
- `MainV2Activity` cold start에서 `Constants.EXTRA_DATA` 또는 audio notification route가 있으면 기존 1초 지연 처리 동안 `LoadingDialog`를 문구 없이 표시한다.
|
||||
- Live route는 공통 1초 대기 종료 후에도 room detail 조회가 진행 중이면 같은 문구 없는 로딩을 유지한다.
|
||||
- Live 상태 조회가 완료되거나 실패하면 로딩을 해제하고, `LiveViewModel.toastLiveData`의 실패 메시지를 기존 Toast 방식으로 표시한다.
|
||||
- Content, Creator, Community, Chat처럼 목적지 판단 자체에 별도 네트워크 조회가 없는 route는 공통 1초 대기가 끝나면 로딩을 해제하고 목적지로 이동한다.
|
||||
- `onNewIntent`처럼 기존 1초 지연이 없는 route에 인위적인 공통 대기를 추가하지 않는다. 단, Live의 실제 room detail 조회 로딩은 동일하게 표시한다.
|
||||
|
||||
#### Edge Cases
|
||||
- 0 이하이거나 파싱할 수 없는 ID는 기존처럼 이동하지 않는다.
|
||||
- Live payload에 room ID와 channel ID가 모두 있어도 room ID를 우선해 Creator Channel로 잘못 이동하지 않는다.
|
||||
- Community payload에 post ID와 creator ID가 모두 있어도 게시글 상세를 우선한다.
|
||||
- Community post ID가 없거나 유효하지 않은 경우에만 creator ID fallback을 사용한다.
|
||||
- `DeepLinkActivity`가 Main으로 Live payload를 전달하는 경우 cold start와 `onNewIntent` 모두 동일한 Live Action 진입점을 사용한다.
|
||||
- 일반 앱 실행에는 공통 딥링크 로딩을 표시하지 않는다.
|
||||
- 공통 1초 대기가 끝나는 시점에 Live room detail 조회가 진행 중이면 로딩을 중간에 해제하지 않는다.
|
||||
- Live 상태 조회가 실패해 목적지로 이동하지 못해도 로딩이 남아 있지 않고 실패 안내가 표시된다.
|
||||
|
||||
---
|
||||
|
||||
## 8. UX / UI Expectations
|
||||
- 로그인, 본인인증, 성인 콘텐츠 설정 안내의 표시 순서와 문구를 유지한다.
|
||||
- 허용된 사용자의 콘텐츠, 라이브, 커뮤니티, 채팅 진입 결과는 기존과 같아야 한다.
|
||||
- 푸시/딥링크에서도 일반 화면과 같은 도메인 접근 정책을 적용하며, 진행 중 Live와 예약 Live의 기존 입장/상세 UX를 유지한다.
|
||||
- cold start 푸시/딥링크의 공통 1초 대기와 Live의 추가 상태 조회 중에는 도메인에 종속되지 않은 문구 없는 spinner를 표시한다.
|
||||
- 기존 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`, 본 작업 문서로 제한한다.
|
||||
- 레거시 파일은 직접 수정하지 않고 기존 기능을 호출하는 `v2` wrapper/adapter를 작성한다.
|
||||
- 변경 범위는 `app/src/main/java/kr/co/vividnext/sodalive/v2`, 대응 테스트, 사용자 승인을 받은 `app/src/main/java/kr/co/vividnext/sodalive/main/DeepLinkActivity.kt`, 필요한 경우 이를 조립하는 `AppDI.kt`, 본 작업 문서로 제한한다.
|
||||
- 레거시 파일은 직접 수정하지 않고 기존 기능을 호출하는 `v2` wrapper/adapter를 작성한다. 이번 후속 범위에서는 V2 진입점으로 유지할 `DeepLinkActivity`만 명시적 예외다.
|
||||
- API -> Repository -> ViewModel -> Activity/Fragment의 기존 흐름을 임의로 깨지 않는다.
|
||||
- 신규 테스트는 Access 판단, Action 입력·출력, route/정책 같은 순수 로직을 우선 검증한다.
|
||||
- 소스 문자열 테스트만으로 정책을 검증하지 않고, 가능한 범위에서 실제 입력·출력 단위 테스트를 추가한다.
|
||||
@@ -238,9 +287,14 @@ Access 판단 결과를 기존 로그인, 본인인증, 설정 이동 UX에 연
|
||||
- `CreatorChannelActivity`와 `HomeOnAirLiveActivity`에 화면 전용 `ensureLoginAndAdultAuth` 구현이 남지 않는다.
|
||||
- 대상 UI 호출부에서 `SharedPreferenceManager.token`으로 개별 행동 접근을 판단하지 않는다.
|
||||
- 오디오, 시리즈, 라이브, 크리에이터, 커뮤니티, 채팅의 합의된 호출부가 각각 단일 Action 진입점을 사용한다.
|
||||
- 보호된 Creator, Community, Chat Action 호출부에 동일한 화면-local 로그인 guard가 중복되지 않는다.
|
||||
- Creator Channel의 Community 작성, 수정, 삭제, 고정 변경 성공 경로가 명시적 `CommunityChange`를 거쳐 하나의 composition handler에서 projection을 갱신한다.
|
||||
- 공통 Action 공개 계약이 feature 전용 UI model 또는 DTO에 의존하지 않는다.
|
||||
- 기존 Intent extra, Activity result, 로그인/인증/설정 UX를 검증하는 회귀 테스트가 통과한다.
|
||||
- 푸시/딥링크의 Content, Creator, Community, Chat 이동은 해당 Action을 사용하고 Live 이동은 `LiveActionCoordinator`를 사용한다.
|
||||
- room ID가 있는 Live payload는 channel ID보다 우선하고, Community post ID는 creator ID보다 우선한다.
|
||||
- Main의 cold start 푸시/딥링크 공통 1초 대기 중 문구 없는 로딩이 표시되고, Live는 추가 상태 조회까지 끊김 없이 유지되며 성공·실패 종료 시 해제된다.
|
||||
- Live 상태 조회 실패 메시지가 사용자에게 노출된다.
|
||||
- 각 Phase 완료 시 중복 제거 전후 호출부 목록과 검증 결과가 `plan-task.md`에 누적 기록된다.
|
||||
|
||||
---
|
||||
|
||||
Reference in New Issue
Block a user