Repository navigation
refactor(platform): ViewModel 오케스트레이션의 UseCase 경계를 전 모듈에서 정리한다 #1688
Copy link
Copy link
Closed
Labels
area:platformCI·빌드·릴리스·저장소 운영 영역CI·빌드·릴리스·저장소 운영 영역refactor동작 변경 없이 코드 구조·책임·경계를 개선하는 작업동작 변경 없이 코드 구조·책임·경계를 개선하는 작업
Description
작업 유형
refactor — 동작 변경 없는 구조·책임·경계 개선
주 담당 모듈
platform — Android 앱·CI·빌드·릴리스·저장소 운영
개요
origin/develop@9471a1359acc 기준으로 ViewModel의 Repository 직접 호출을 전 모듈에서 다시 감사했다. 목적은 모든 호출을 기계적으로 UseCase로 감싸는 것이 아니라, 여러 호출의 순서·부분 실패·취소·재사용·도메인 불변식을 실제로 소유하는 경계만 UseCase로 분리하는 것이다.
이번 감사의 출발점은 #246에서 제거한 SaveAfternoteUseCase다. 당시 남은 구현은 단일 호출처의 sealed command dispatch뿐이었지만, command는 domain에 유지되고 동일한 dispatch는 ViewModel로 이동했다. 이를 “유스케이스로서 부적합”이라고 일반화하지 않고, 저장 명령의 애플리케이션 경계로 복원한다.
공통 판단 기준
다음 중 하나 이상을 실제로 소유할 때만 UseCase 후보로 본다.
- 둘 이상의 Repository·UseCase 호출 순서와 short-circuit
- 부분 성공·부분 실패·fallback·보상 결과
- 취소 전파와 시간 예산·재시도·폴링
- 여러 ViewModel이 재사용하는 동일 정책
- UI와 무관한 도메인 불변식 또는 폐쇄된 command 해석 경계
다음은 계속 ViewModel에서 Repository를 직접 호출한다.
- 단일 상세 조회·삭제·paging·캐시 I/O
- 화면 입력 정규화, 중복 탭 방지, 로딩·문구·navigation event
- Android
Uri·리소스처럼 presentation에 남아야 하는 처리
UseCase 위치는 이름만 보고 domain 모듈로 강제하지 않는다. cross-feature 집계는 의존 방향을 지킬 수 있는 application/presentation usecase에 둘 수 있고, domain 타입·Repository만 쓰는 경계만 domain에 둔다.
감사 결과와 우선순위
우선순위 점수는 (Impact + Risk) × (6 - Effort)이며 각 축은 1~5다.
| 순위 | 경계 | I/R/E | 점수 | 비고 |
|---|---|---|---|---|
| 1 | 수신자 마스터키 검증 → 전역 인증 컨텍스트 커밋 | 5/5/3 | 30 | #607·#1554 착지 후 |
| 2 | 수신자 이메일 검증 → 발신자별 인증 캐시 커밋 | 4/3/2 | 28 | 두 번째 suspend 실패 포함 |
| 3 | 수신자 홈 4-source 집계·부분 실패 | 5/3/3 | 24 | #1462 이동 후 |
| 4 | 설정 수신자 정보 → 마지막 인사 2단계 저장 | 4/4/3 | 24 | 부분 성공을 명시 |
| 5 | 설정 전달조건 보존·합성 | 4/4/3 | 24 | 다른 content type 보존 |
| 6 | 마음기록 draft 조회·일괄 삭제 | 4/3/3 | 21 | 3개 VM 재사용·부분 삭제 |
| 7 | 애프터노트 저장 command 해석 | 3/2/2 | 20 | SaveAfternoteUseCase 복원 |
| 8 | 타임레터 생성·수정·등록 게이트 | 4/4/4 | 16 | #470·#881 계약과 조율 |
| 9 | 주간 리포트 조회·제한 폴링 | 4/4/4 | 16 | 가상 시간 검증 |
| 10 | 추억 공간 다중 조회·부분 실패 | 3/4/4 | 14 | 음수 ID 우회 제거 |
단계
- 독립적이고 저비용인 애프터노트 저장 경계와 설정 저장 정책부터 분리한다.
- 이동·계약 선행 이슈가 끝난 뒤 수신자 홈과 인증 흐름을 분리한다.
- 마음기록과 타임레터의 다중 조회·저장 정책을 순수 결과 계약과 단위 테스트로 고정한다.
공통 완료 조건
- 아래 자식 이슈가 각 모듈 담당자에게 자동 배정되고 선행 이슈가 명시된다.
- 각 UseCase가 소유하는 순서·부분 실패·취소·불변식을 domain/application 단위 테스트로 검증한다.
- ViewModel에는 UI 상태·문구·telemetry·navigation 매핑만 남긴다.
- 프로덕션 visibility를 테스트 때문에 넓히지 않는다.
- 기존 동작과 오류 복구 경계를 바꾸지 않으며, 바뀌어야 하는 계약은 별도 기능/버그 이슈로 분리한다.
자식 이슈
- refactor(afternote): SaveAfternoteUseCase를 저장 명령 경계로 복원한다 #1694 — 애프터노트 저장 command 경계 복원
- refactor(home): 수신자 홈 4-source 집계 정책을 UseCase로 분리한다 #1689 — 수신자 홈 4-source 집계
- refactor(receiver): 수신자 인증 검증·세션 활성화 정책을 UseCase로 분리 #1690 — 수신자 인증 검증·세션 활성화
- refactor(receiver): 열람 신청 서류 제출 불변식을 UseCase로 분리 #1701 — 열람 신청 서류 제출 불변식
- refactor(setting): 수신자 저장·전달조건 정책을 UseCase로 분리한다 #1691 — 설정 수신자 저장·전달조건
- refactor(mindrecord): 다중 조회·삭제·폴링 정책을 UseCase 경계로 이동한다 #1693 — 마음기록 다중 조회·삭제·폴링
- refactor(timeletter): 생성·수정·등록 게이트를 SaveTimeLetterUseCase로 수렴한다 #1692 — 타임레터 생성·수정·등록 게이트
참고
- refactor(afternote): SaveAfternoteUseCase 제거 검토 — Command sealed 라우팅만 하는 약한 UseCase #246:
SaveAfternoteUseCase제거 당시 판단 - Android 공식 가이드의 domain layer는 선택 사항이므로 “Repository 직접 호출 0건”을 목표로 하지 않는다.
- 정확히 중복되는 열린 UseCase 구조 이슈는 현재 없다. 기존 기능·버그 이슈와 파일이 겹치는 경우 각 자식 이슈에 선행·조율 관계를 적는다.
Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
area:platformCI·빌드·릴리스·저장소 운영 영역CI·빌드·릴리스·저장소 운영 영역refactor동작 변경 없이 코드 구조·책임·경계를 개선하는 작업동작 변경 없이 코드 구조·책임·경계를 개선하는 작업
Activity
fix(mindrecord): 삭제 실패 계측을 전건으로 되돌리고 재진입 이름 갱신을 고친다 (#1693)
OnlyReadOneBook commentedon Sep 13, 2026
착수 전 점검 결과 이 총괄 이슈의 감사표가 현황과 어긋나 있어 대조 결과를 남깁니다. 기준 리비전은
origin/develop86ab223 이고, 본문이 인용한9471a1359acc에서 681 커밋 뒤입니다.감사표 10행 대조
착지한 것 4행.
GetReceiverHomeSummaryUseCase.kt(abb9f8e, #1689)LoadMindRecordDraftsUseCase.kt·DeleteMindRecordDraftsUseCase.kt(#1875 → #1693)ObserveWeeklyReportUseCase.kt·GetWeeklyRecordCountUseCase.kt(2e4ddd6, #1875)GetMemorySpaceUseCase.kt(f2a878e, #1693)표 밖의 #1701 열람 신청 서류 제출 불변식도
SubmitDeliveryVerificationUseCase.kt(9e64c0d)로 닫혔습니다.남은 6행.
MasterKeyViewModel.kt:47-48,70-73. 본문의 게이트 "받은 기록함이 서버 목록 API 를 쓰지 않고 로컬 stub 으로 동작 — record-boxes 미연동 #607 · refactor(receiver): 마스터 키 UUID 의 이름 3종과 fromRaw 의 숨은 와이어 관용 정리 #1554 착지 후" 는 낡았습니다. refactor(receiver): 마스터 키 UUID 의 이름 3종과 fromRaw 의 숨은 와이어 관용 정리 #1554 는 닫혔고 받은 기록함이 서버 목록 API 를 쓰지 않고 로컬 stub 으로 동작 — record-boxes 미연동 #607 은 열려 있으며, refactor(receiver): 수신자 인증 검증·세션 활성화 정책을 UseCase로 분리 #1690 본문이 2026-09-08 에 security(receiver): 기록함 접근 자격·마스터키 비노출 서버 계약을 확정하고 검증한다 #1949 · security(receiver): 다중 수신자 컨텍스트·키 보관 정책을 서버 기록함 기준으로 분리 확정한다 #1950 을 선행으로 추가했습니다.IdentityVerificationViewModel.kt:47-48,107-110.ReceiverEditViewModel.kt:26,81,100)이나 PR refactor: 설정 MVI와 저장·Repository 책임을 최종 계약으로 통합 (#1502) #1967 브랜치에UpdateReceiverInfoUseCase.kt로 이미 구현돼 승인 대기 중입니다.DeliveryConditionViewModel.kt:30,112, 같은 브랜치에UpdateTimeLetterDeliveryConditionUseCase.kt).AfternoteEditorViewModel.kt:533의 privateexecuteSaveCommand가 그대로이고:528-532KDoc 이 아직 "약한 UseCase(refactor(afternote): SaveAfternoteUseCase 제거 검토 — Command sealed 라우팅만 하는 약한 UseCase #246) 로 판단해 ViewModel 로 흡수" 라고 적혀 있습니다. PR refactor(afternote): SaveAfternoteUseCase 로 저장 명령 해석 경계를 복원한다 (#1694) #1838(APPROVED)이 이걸 되돌립니다.TimeLetterWriteViewModel.kt:458은createTimeLetterUseCase,:468은timeLetterRepository.updateTimeLetter직접 호출로 비대칭이 그대로고,:174의getTimeLetters().totalCount한도 조회도 VM 에 남아 있습니다. 담당은 koongmai.자식 목록이 현황을 반영하지 않는다
#1689 · #1693 · #1701 은 닫혔고, #1690 은 #607 에 blocked, #1692 는 koongmai, #1694 는 PR #1838 승인 대기입니다. 문제는 #1691 입니다. 그 구현이 자식 트리 밖의 PR #1967(#1502 설정 MVI 통합)에 흡수돼 본문이 "#1967 머지로 종료" 로 바뀌었습니다. 참고란의 "정확히 중복되는 열린 UseCase 구조 이슈는 현재 없다" 도 낡았습니다. #1693 이 #1875 로 한 번 더 갈라졌습니다.
공통 완료 조건
"ViewModel 에는 UI 상태·문구·telemetry·navigation 매핑만 남긴다" 는 전 모듈 기준으로 미달성입니다. 위 1 · 2 · 4 · 5 · 7 · 8 행이 남아 있습니다. 나머지 세 조건(단위 테스트 · visibility · 동작 보존)은 닫힌 자식 PR 들이 각자 충족했고 #1688 자체가 검증할 대상은 없습니다.
"자식 이슈가 각 모듈 담당자에게 자동 배정" 은 배정 자체는 됐지만 receiver(#1690) · setting(#1691) · afternote(#1694) 세 모듈이 한 사람에게 몰려 있습니다. 그사이 모듈 담당이 바뀌었을 수 있어 규칙 위반으로 단정하지는 않습니다.
git log --grep '#1688' origin/develop은 0건이고 워크트리도 없습니다. 이 이슈 자체는 코드 범위가 0인 총괄이라 그게 맞습니다.제안
지금 상태로는 감사표를 보고 무엇이 남았는지 알 수 없습니다. 세 가지를 정리하면 총괄로서 다시 쓸모가 생깁니다.
OnlyReadOneBook commentedon Sep 23, 2026
추적 역할이 끝나 닫는다 (2026-09-23)
이 이슈는 코드 범위가 0인 총괄이다. 감사표 10행이 전부 착지했거나, 결정 대기로 닫혔거나, 담당 큐의 하위 이슈와 열린 PR이 쥐고 있다. 부모에 남길 일이 없으므로 #928·#945 와 같은 방식으로 닫는다. 기준은
origin/developf69332d 다.표 밖의 #1701 열람 신청 서류 제출 불변식도 9e64c0d 로 착지했다.
행별 보정
MasterKeyViewModel.kt:73이receiverRepository.saveMasterKey를,IdentityVerificationViewModel.kt:110이identityVerificationRepository.markVerified를 직접 부른다. 마스터키 보관 방식 자체가 그 결정의 대상이라, 어느 결정에도 살아남는 조각으로 떼어 낼 수 없다. 결정이 나면 최신 코드 기준으로 새 이슈를 등록한다.ReceiverEditViewModel.kt:81,100,DeliveryConditionViewModel.kt:112는 그대로다. PR refactor(setting): 수신자 저장 정책을 UseCase로 분리 (#1691) #2124 가UpdateReceiverInfoUseCase·UpdateTimeLetterDeliveryConditionUseCase로 가져가고, base 는 PR refactor(setting): 설정 전용 저장소와 좁은 주입 계약 분리 (#1429) #2123(refactor/1429-setting-repositories)이다. 0913 코멘트가 적은 PR refactor: 설정 MVI와 저장·Repository 책임을 최종 계약으로 통합 (#1502) #1967 은 머지되지 않고 닫혔고, refactor(setting): 수신자 저장 정책을 UseCase로 분리 (#1691) #2124 가 그 저장 정책만 현재 코드에 맞춰 다시 옮겼다.AfternoteEditorViewModel.kt:539의 privateexecuteSaveCommand와:536의 KDoc 이다. 0913 코멘트의:533은 낡았다. PR refactor(afternote): SaveAfternoteUseCase 로 저장 명령 해석 경계를 복원한다 (#1694) #1838 은 base 가 PR refactor(afternote): 합본 UserRepository 대신 좁은 계약 UserReceiverRepository·MyProfileRepository 를 주입한다 (#1743) #1837 이고 그 밑이 fix(afternote): 프로세스 사망 복원 뒤 프리필이 미저장 편집을 덮지 않게 한다 (#1732) #1839(fix/1732-process-death-prefill-guard)인 스택이라, 밑에서부터 머지돼야 develop 에 들어온다.TimeLetterWriteViewModel.kt:468이timeLetterRepository.updateTimeLetter를 직접 부르고,:174의 한도 조회도 ViewModel 에 남아 있다. refactor(timeletter): 생성·수정·등록 게이트를 SaveTimeLetterUseCase로 수렴한다 #1692 가 추적한다.하위와의 관계는 본문 목록에만 있고 GitHub sub-issue 관계는 걸려 있지 않다. 이 이슈를 닫아도 하위 상태는 바뀌지 않으며, PR #1838 은
Refs #1688로만 걸려 있어 영향이 없다.