Skip to content

refactor(platform): MVI 아키텍처 전환 총괄 — ViewModel 51개 #1811

Closed as not planned
2 of 3 issues completed
Closed as not planned

Description

@OnlyReadOneBook

작업 유형

refactor — 동작 변경 없는 구조·책임·경계 개선

주 담당 모듈

platform — Android 앱·CI·빌드·릴리스·저장소 운영

개요

프레젠테이션 계층 전체를 MVI 로 전환하는 총괄 이슈. 실제 작업은 자식 이슈에서 한다.

도입하는 것

RuleUp-ASM/Android 의 MviViewModel 과 같은 계약이다. 흔히 «MVI» 라 불리는 것 중에서도 엄격한 쪽이고, 여기에 Intent / ReducerEvent 2단 분리가 더해져 있다.

abstract class MviViewModel<I : MviIntent, S : UiState, E : ReducerEvent>(
    initialState: S,
) : ViewModel() {
    abstract fun onIntent(intent: I)                       // 외부 진입점 하나
    protected abstract fun reduce(state: S, event: E): S    // 전이는 순수 함수로 강제
    protected fun dispatch(event: E)                       // 상태 변경은 이 경로만
}
// 일회성 이벤트는 Channel 이 아니라 UiState 의 nullable 신호로 나른다 — 레포 정본 #228
  • Intent — 사용자가 하려는 것(SubmitDescription, SelectFilter, ConsumeSaveResult)
  • ReducerEvent — 상태가 겪은 것(Loading, Loaded, DraftFailed)
  • Intent 하나가 ReducerEvent 를 0개에서 N개까지 낳는다. 네비게이션만 하는 Intent 는 0개, 로드 Intent 는 Loading → Loaded 2개.

전환 규모 (develop 실측)

모듈 ViewModel Screen on* 콜백 presentation @Test 특이
afternote 9 12 37 246 Consumed 3 · 에디터 29 fun
mindrecord 10 10 64 232 이벤트 규약 없음
setting 11 17 88 46 Channel 5
timeletter 7 7 45 20
receiver 8 9 44 46 Consumed 5 · onEvent 1
onboarding 3 9 53 23 Consumed 3
home 1 1 1 16
app 2 — — —
합계 51 64 332 629

지금 무엇이 갈려 있나

얻는 것

  1. 전이가 reduce 한 함수에 모인다
  2. 리듀서 테스트가 코루틴 하네스 없이 순수 함수로 돌아간다 — @Test 629개가 지금 짊어진 비용의 상당 부분
  3. 일회성 신호의 소비 경로가 Intent.ConsumeXxx 하나로 접힌다 — 신호를 UiState 에 흡수하는 규약(refactor: ViewModel 이벤트 → UI state 흡수 마이그레이션 (전수 9개 VM) #228)은 그대로 두고, VM 마다 흩어진 onXxxConsumed() public fun 만 사라진다
  4. 화면이 Screen(stateful) / Content(internal stateless) 2단으로 갈려 프리뷰·screenshotTest 진입점이 명확해진다

진행 순서

#1811 전체 총괄
  ├─ #1812  core·platform·onboarding·receiver·afternote 몫   @1hyok
  │    ├─ #1800 core 베이스        ← 세 역할 전부의 선행
  │    ├─ #1801 konsist + 문서
  │    ├─ #1802 onboarding 파일럿  ← 세 역할 전부의 선행 (관용구 확정)
  │    ├─ #1803 receiver  · #1804 afternote
  │    ├─ #1809 잔여분 정리 (마지막)
  │    └─ #1810 Nav3 접점 판정 — 코드 0줄
  ├─ #1813  setting·timeletter 몫                            @koongmai
  │    └─ #1805 setting · #1806 timeletter
  └─ #1814  mindrecord·home 몫                               @Sadturtleman
       └─ #1807 mindrecord · #1808 home

역할별 총괄 아래에서만 착수한다. 담당자는 자기 총괄 하나만 보면 되고, 진행률도 역할 단위로 읽힌다.

#1809 에서 konsist allowlist 가 비고 Consumed 가 레포 전체 0건이 되면 전환이 끝난다.

#1800 과 #1802 는 세 역할 전부의 선행이다 — 이 둘이 늦어지면 나머지 두 역할이 통째로 막힌다. #1812 가 그 책임을 진다.

범위에서 뺀 것

자식 이슈

참고

참조 구현: RuleUp MviViewmodel.kt · HomeViewModel · HomeContract · HomeScreen

관련 선행 작업(closed): #283 (mutableStateOf → MutableStateFlow 통일) · #674 · #675 (에디터 폼 갱신 통로 재설계)

Activity

  1. added
    refactor동작 변경 없이 코드 구조·책임·경계를 개선하는 작업
    area:platformCI·빌드·릴리스·저장소 운영 영역
    on Sep 3, 2026
  2. 1 remaining item

  3. OnlyReadOneBook commented on Sep 13, 2026

    @OnlyReadOneBook
    CollaboratorAuthor

    실측 갱신 (origin/develop 7d87aa3f4, 2026-09-13)

    총괄이라 이 이슈 자체로 뗄 독립 범위가 없다. blockedBy 도 0건이다. 대신 본문의 수치와 소유자 표기가 지금 develop 과 어긋난 곳이 있어서, 다음 착수자가 묵은 전제로 시작하지 않도록 실측으로 갱신해 둔다. 코드 변경 0줄.

    이미 develop 에 들어간 것

    산출물 경로 이슈
    MVI 베이스 core/ui/src/main/kotlin/com/afternote/core/ui/mvi/MviViewModel.kt · MviContract.kt · ObserveSignal.kt #1800 (closed)
    계약 가드 konsist/src/test/kotlin/com/afternote/konsist/MviContractKonsistTest.kt #1801 (closed)
    규약 문서 docs/convention/mvi.md #1801 (closed)

    베이스와 가드와 문서는 끝났다. 남은 것은 모듈별 ViewModel 전환뿐이다.

    1. 전환 규모는 51개가 아니라 52개

    모듈 본문 develop 실측 델타 원인
    afternote 9 9
    mindrecord 10 10
    setting 11 11
    timeletter 7 7
    receiver 8 7 ReceiverHomeViewModel 이 feature/home 으로 옮겨 갔다
    onboarding 3 4 #457 이 FindPasswordViewModel 을 들여왔다
    home 1 2 위 이동분이 들어왔다
    app 2 2
    합계 51 52

    재현:

    git ls-tree -r --name-only origin/develop \
      | grep -E 'ViewModel\.kt$' \
      | grep -vE '/(test|androidTest)/' \
      | grep -v '^core/ui/'   # 베이스 클래스 자신은 뺀다

    2. 단일 진입점 ViewModel 의 소재와 소관이 바뀌었다

    본문의 "단일 진입점은 51개 중 1개다. ReceiverHomeViewModel.onEvent" 는 여전히 사실이지만 소재가 다르다.

    3. 종료 조건의 소유자는 #1809 가 아니라 #2022

    본문 끝의 "#1809 에서 konsist allowlist 가 비고 Consumed 가 레포 전체 0건이 되면 전환이 끝난다" 는 이제 #2022 다.

    4. konsist allowlist 가 비어도 app 모듈은 안 덮인다

    종료 검사(#2022)를 맡는 쪽이 먼저 알아야 할 것. 규칙 B 의 위반 후보가 경로로 한 번 걸러진다.

    val FEATURE_PRESENTATION_MAIN = Regex("""feature/[^/]+/presentation/src/main/""")

    실측하면 이렇게 갈린다.

    5. onXxxConsumed 실측

    본문의 "11개 VM(참조 77곳)" 은 develop 에서 이렇게 된다. #2022 체크리스트에도 같은 수치가 복사돼 있어 함께 정정 대상이다.

    항목 본문 develop 실측
    선언한 ViewModel 11 12
    fun on*Consumed 선언 22
    main 소스 참조 77 85
    테스트까지 포함 113

    재현:

    git ls-tree -r --name-only origin/develop | grep '\.kt$' \
      | while IFS= read -r f; do git show "origin/develop:$f"; done \
      | grep -oE '\bon[A-Za-z0-9]*Consumed\b' | wc -l

    참조 수에서 KDoc 안의 플레이스홀더 onXxxConsumed 3건(MviContract.kt, OnboardingProfileEntry.kt, SignUpUiState.kt)은 뺐다. 실제 심볼이 아니라 설명문이다.

    setting 의 Channel 5곳은 본문 그대로 맞다. VM 기준으로도 5개이고 #1502 몫이다.

    6. 총괄 트리에 안 걸린 MVI 이슈가 8건 있다

    GraphQL parent 가 NONE 인 것들이다. 이 8건이 어느 역할 총괄에도 안 달려 있어서 #1812 진행률이 2/7 로 실제보다 적게 읽힌다.

    이슈 담당 범위 붙일 자리
    #1968 1hyok afternote 편집기 외 ViewModel 8개 #1812
    #2022 1hyok 최종 앱 루트 책임 판정과 종료 검사 #1812
    #1829 Sadturtleman onboarding Content seam 5건 (#1802 후속) #1812
    #1502 1hyok setting 이벤트 스트림, #1805 의 선행 #1813
    #1821 koongmai timeletter 화면 no-op 콜백 24건 #1813
    #2037 Sadturtleman ReceiverHomeViewModel 과 수신자 홈 화면 #1814
    #1818 Sadturtleman mindrecord Content seam visibility #1814
    #1820 Sadturtleman mindrecord 화면 no-op 콜백 31건 #1814

    붙일 자리는 제안이고 아직 아무것도 걸지 않았다. #1811·#1812·#1813 은 1hyok 소유라 그 밑 5건은 이쪽에서 sub-issue 로 걸 수 있다. #1814 는 Sadturtleman 소유라 그 밑 3건은 담당자가 직접 걸도록 목록으로만 남긴다.

    7. 진행 순서 트리의 담당자 표기가 실제와 다르다

    본문 "진행 순서" 트리와 GitHub assignee 가 어긋난 곳이 둘이다.

    8. 임계경로는 #1802 하나로 좁혀졌다

    지금 열려 있는 MVI PR

    이 이슈에서 손댈 코드가 없다고 판정한 근거이기도 하다. MviContractKonsistTest.kt 한 파일을 열린 PR 6건이 동시에 물고 있다.

    PR 이슈 리뷰 base konsist 가드 파일
    #1825 #1802 onboarding APPROVED refactor/1863-onboarding-failure 건드림
    #2039 #1803 receiver REVIEW_REQUIRED develop 건드림
    #1972 #1804 afternote APPROVED refactor/1902-memorial-photo 건드림
    #2021 #1968 afternote 없음 refactor/1804-editor-mvi 건드림
    #2018 #1805 setting 없음 refactor/1502-setting-state-mvi 건드림
    #1967 #1502 setting APPROVED codex/1695-setting-nav3 건드림
    #2033 #1809 app REVIEW_REQUIRED develop 안 건드림
    #2038 #1810 문서 REVIEW_REQUIRED develop 안 건드림

    docs/convention/mvi.md 와 core/ui/.../MviContract.kt 는 #1825 와 #2038 이 물고 있다. 남은 모듈(mindrecord·timeletter·home)은 다른 담당자 몫이다. 이 상태에서 총괄이 코드를 새로 쓰면 충돌만 만들고, 소비처 없는 계약이 하나 더 생긴다.

    다음 착수자에게

    1. 본문 표와 트리는 위 1·2·3·7 로 읽는다. 특히 51 이 아니라 52 이고, 종료 검사는 refactor(platform): 최종 앱 루트 책임 판정과 MVI 전환 종료 검사 (#1809 분리) #2022 다.
    2. 착수 전에 refactor(onboarding): MVI 전환 파일럿 — ViewModel 3개 + 신호 소비를 Intent 로 #1802 (PR refactor(onboarding): 실패 사유와 표시 채널을 분리한다 (#1863) #1963 → refactor(onboarding): ViewModel 3개를 MVI 로 옮기고 소비를 Intent 로 접는다 (#1802) #1825) 가 들어갔는지 본다. 안 들어갔으면 setting·timeletter·mindrecord·home 은 관용구가 아직 안 굳은 상태다.
    3. 종료 검사를 짤 때 allowlist 가 비는 것을 종료 신호로 삼지 않는다. app 모듈 2개는 가드 밖이고, 그 둘은 refactor(platform): 알림 권한 ViewModel과 Effect를 MVI로 전환 #1809 와 refactor(platform): 최종 앱 루트 책임 판정과 MVI 전환 종료 검사 (#1809 분리) #2022 가 들고 있다 (위 4).
  4. OnlyReadOneBook commented on Sep 23, 2026

    @OnlyReadOneBook
    CollaboratorAuthor

    하위 이슈만 묶어 두는 총괄 이슈는 두지 않기로 정리하면서 닫습니다(2026-09-23). 이 이슈에는 자기 코드 몫이 없고, 남은 전환은 담당자별 이슈가 각자 진행합니다.

    선행 관계(#1800·#1802 기반)는 하위 이슈에 blocked_by 로 걸려 있습니다. 위 전환 규모 표와 계약 설명은 이 본문에 그대로 남겨 둡니다.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

area:platformCI·빌드·릴리스·저장소 운영 영역refactor동작 변경 없이 코드 구조·책임·경계를 개선하는 작업

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions