ToDo 앱을 만들면서 클린 아키텍처 구조와 Riverpod Provider 구조를 어떻게 설정해야 하는지 꽤 깊게 파고들었다.
처음엔 구조가 좀 헷갈렸는데, 전체 의존성 흐름을 정리하면서 ‘왜 이렇게 해야 하는가’가 명확해졌다.
1. Domain–Data–Presentation 구조 다시 이해하기
가장 핵심은 이 한 문장이다.
의존성의 방향은 바깥에서 안쪽으로만 흐른다.
Presentation → Domain → Data
즉,
- Domain은 안쪽 계층이라 아무것도 의존하면 안 된다.
- Presentation(ViewModel)은 Data를 직접 의존하면 안 된다.
- Data에서는 Domain 모델을 사용해야 하고,
- Presentation에서는 Domain 인터페이스(IToDoRepository)만 사용해야 한다.
오늘 내가 실수했던 건 ViewModel이 Data 레이어의 Repository 구현체까지 가져다 쓰는 구조였다.
이건 결국 의존성 역전 원칙(DIP)을 깨는 방식이라서 바람직하지 않았다.
2. Repository Provider는 어디에 있어야 하는가?
처음에는 ViewModelProvider만 만들고 Repository Provider는 안 만들려고 했는데, 다시 생각해보면 이 방식이 확실히 구조적으로 어색했다.
왜냐면 ViewModel은 DataImpl을 직접 알면 안 되고, DataImpl은 Provider로 감싸서 인스턴스를 외부에 노출시켜줘야 하기 때문.
그래서 결론을 이렇게 냈다:
- Repository Provider는 Data Layer에서 만든다.
- ViewModel은 ref.read(toDoRepositoryProvider) 로 가져다 쓰기만 한다.
이 방식이면 ViewModel은 인터페이스만 의존하고, 실제 구현체는 Provider가 주입해주기 때문에 클린 아키텍처 구조가 딱 맞는다.
3. Freezed Model 필드 required 문제 해결
내 ToDoModel을 Freezed로 만들면서 isDone을 required로 넣어놨는데, AddToDoDialog에서 이 값을 넘기지 않아서 오류가 났다.
해결은 간단했다.
그냥 생성 시 명시적으로 넣어주면 된다.
이 때문에 Domain Model은 애초부터 필요한 값들을 확실하게 강제하게 되고, 앱 전체에서 일관성이 유지된다.
4. DTO ↔ Domain Mapper 구성 정리
ToDoDto → ToDoModel 변환
ToDoModel → ToDoDto 변환
매퍼는 매우 깔끔하게 동작한다.
오늘 확인하면서 Domain과 DTO가 서로 깔끔하게 대응되는지 확실히 정리했다.
이 구조 덕분에 Repository에서는 오직 DTO만 다루고,
Presentation에서는 오직 Domain Model만 다루도록 자연스럽게 강제된다.
5. Repository의 lastDoc 문제 해결
처음에는 ViewModel에서 Repository.lastDoc을 직접 초기화하고 있었는데,
이건 Repository 내부 상태를 ViewModel에서 건드리는 형태라 역할이 뒤섞인다.
결국 reload()에서 lastDoc을 초기화하는 방식으로 옮기면서
내부 상태는 Repository에서만 관리하도록 정리했다.
6. 오늘 최종 결론 (아키텍처 기준)
오늘 정리한 내용을 한 문장으로 요약하면:
ViewModel은 Domain 인터페이스를 바라보고, 실제 사용되는 Repository는 Provider가 DI(의존성 주입)해주는 방식이 가장 깔끔하다.
지금 구조는 다음처럼 정리된다.
그리고 Repository Provider가 Data Layer에 존재하기 때문에
ViewModel은 그냥 읽기만 하면 된다.
이제 보니까 구조적으로 꽤 단단해졌다는 느낌이 든다.
이번 과제에서 배운 점 요약
- 클린 아키텍처의 의존성 방향을 다시 정확하게 잡아냈다.
- Repository Provider는 Data Layer에 두고, ViewModel은 DI로 받아야 한다.
- Domain Model을 Freezed로 구성하면 안정성과 일관성이 생긴다.
- lastDoc 같은 내부 상태는 Repository에서만 관리하게 해야 한다.
- 전체 레이어가 자연스럽게 독립적으로 동작하도록 조직한 덕분에 확장성도 좋아졌다.