
안드로이드 Repository 패턴은 앱 구조 설명에서 자주 나오지만, 단순히 파일을 하나 더 만드는 규칙으로 이해하면 금방 부담스러워집니다.
핵심은 ViewModel이 데이터 출처를 몰라도 되게 만드는 경계입니다. 이 글은 Android 공식 앱 아키텍처 문서를 기준으로 Repository가 필요한 순간과 과한 추상화가 되는 순간을 함께 정리합니다.

안드로이드 Repository 패턴을 한 줄로 정리하면
Repository는 ViewModel과 실제 데이터 소스 사이에 놓는 데이터 접근 경계입니다. ViewModel은 화면에 필요한 상태를 만들고, Repository는 그 상태를 만들 재료를 어디에서 가져올지 담당합니다.
class UserViewModel(
private val userRepository: UserRepository
) : ViewModel() {
val uiState = userRepository.observeUser()
.map { user -> UserUiState(user.name, user.email) }
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5_000), UserUiState.Empty)
}이 구조에서는 ViewModel이 Retrofit인지 Room인지 DataStore인지 알 필요가 없습니다. 화면 로직은 데이터 출처가 아니라 데이터의 의미에 집중합니다.
ViewModel이 데이터 소스를 직접 알면 생기는 문제
작은 예제에서는 ViewModel에서 API를 바로 호출해도 동작합니다. 하지만 기능이 늘면 같은 데이터 조회 규칙, cache 정책, 오류 처리, offline fallback이 여러 ViewModel에 흩어지기 쉽습니다.
- 같은 API 호출을 여러 화면에서 반복한다
- Room 저장 여부와 refresh 기준이 화면마다 달라진다
- 테스트에서 네트워크와 DB 준비 비용이 커진다
- 화면 요구사항이 데이터 저장 규칙까지 건드리게 된다
Repository가 실제로 맡는 일
Repository는 단순 전달자가 아닙니다. 좋은 Repository는 데이터 접근 규칙을 모읍니다. remote source와 local source를 조합하고, cache된 값을 먼저 보여줄지 새로고침을 기다릴지 결정합니다.
class UserRepository(
private val api: UserApi,
private val dao: UserDao
) {
fun observeUser(): Flow<User> = dao.observeUser()
suspend fun refreshUser(id: String) {
val remote = api.getUser(id)
dao.upsert(remote.toEntity())
}
}이렇게 나누면 ViewModel은 refresh 요청만 보내고, 저장 방식과 변환 규칙은 Repository 안에서 관리됩니다.
테스트에서 fake repository가 주는 이점
Repository 경계가 있으면 ViewModel 테스트에서 Retrofit, Room, 실제 디스크를 준비하지 않아도 됩니다. 같은 인터페이스를 구현한 fake repository를 넣고 화면 상태 변화만 확인할 수 있습니다.
class FakeUserRepository : UserRepositoryContract {
private val users = MutableStateFlow(User("Kim", "kim@example.com"))
override fun observeUser(): Flow<User> = users
}테스트가 빨라지면 화면 로직을 더 자주 검증할 수 있습니다. Repository 패턴의 중요한 가치는 ‘예쁜 구조’보다 이런 검증 가능성에 있습니다.
Repository를 과하게 만들지 않는 기준
- 데이터 출처가 하나이고 재사용도 없다면 먼저 단순하게 시작한다
- Repository가 API 메서드를 이름만 바꿔 전달한다면 역할이 약한지 의심한다
- 여러 화면이 같은 데이터 규칙을 공유하기 시작하면 경계를 만든다
- remote/local/cache/error mapping이 섞이면 Repository로 옮긴다
- 테스트에서 fake로 대체할 가치가 있는지 확인한다
정리
Repository를 이해할 때는 명령어나 패턴 이름만 외우기보다 그 도구가 해결하려는 경계를 먼저 보는 편이 좋습니다. 오늘 글의 기준은 화면과 데이터, 실행과 import, 정상 흐름과 오류 흐름, 정합성과 동시성, 개인 브랜치와 공유 브랜치를 나눠 보는 것입니다.
함께 보면 좋은 내부 글은 ViewModel은 왜 필요한가, collectAsStateWithLifecycle은 왜 필요할까, 안드로이드 앱 구조 입문 시리즈입니다. 외부 기준은 Android Developers – Guide to app architecture, Android Developers – Data layer, Android Developers – ViewModel overview를 확인했습니다.