면접 질문 목록으로 가기
면접 질문실전 질문꼬리 질문

확장 가능한 안드로이드 뉴스 앱 아키텍처

skydovesJaewoong Eum (skydoves)||11분 소요

확장 가능한 안드로이드 뉴스 앱 아키텍처

단순한 프로토타입 수준을 넘어서는 안드로이드 애플리케이션에는 명확한 관심사 분리(separation of concerns), 예측 가능한 데이터 흐름, 그리고 기능 확장에 맞춰 성장할 수 있는 계층 구조가 필수적입니다. Google의 공식 아키텍처 가이드는 이러한 원칙을 UI LayerData Layer로 구성된 계층형 시스템으로 정리하고 있으며, 복잡한 비즈니스 로직이 필요한 경우 선택적으로 Domain Layer를 추가할 수 있도록 설계되어 있습니다. 이 계층들이 어떻게 상호작용하는지, 데이터가 각 계층을 어떻게 흘러가는지, 그리고 각 구성요소가 독립적으로 테스트 가능한 상태를 어떻게 유지하는지 이해하는 것은 시니어 안드로이드 면접에서 가장 빈번하게 다뤄지는 주제 중 하나입니다. 실무에서도 아키텍처 설계 능력은 주니어와 시니어를 구분 짓는 핵심 역량이므로, 깊이 있게 학습해 두시길 권장합니다.

이번 면접 질문을 통하여 아래 내용들을 학습하실 수 있습니다.

  • 안드로이드 애플리케이션에서 UI Layer, Data Layer, 그리고 선택적 Domain Layer 각각의 역할과 책임을 설명할 수 있습니다.
  • 오프라인 우선(offline-first) 데이터 관리를 위한 리포지토리 패턴(Repository Pattern)과 단일 진실 공급원(Single Source of Truth) 원칙을 기술할 수 있습니다.
  • 사용자 액션이 ViewModel을 거쳐 Data Layer까지 전달되고 다시 UI로 돌아오는 단방향 데이터 흐름(Unidirectional Data Flow)을 추적할 수 있습니다.
  • UseCase 클래스를 활용한 Domain Layer 도입 시점을 판단할 수 있습니다.
  • 생명주기 인식 상태 수집, 의존성 주입(dependency injection), 그리고 다중 계층 테스트 전략을 적용할 수 있습니다.

계층형 아키텍처와 관심사 분리

Google이 권장하는 아키텍처는 안드로이드 애플리케이션을 두 개의 필수 계층과 하나의 선택적 계층으로 나눕니다. UI Layer에는 컴포저블(Composable) 또는 View와 ViewModel이 포함됩니다. 컴포저블은 상태를 렌더링하고 사용자 입력을 캡처하는 역할을 담당하며, ViewModel은 상태 홀더(state holder)로서 UI로부터 이벤트를 수신하고, 하위 계층에 작업을 위임한 뒤, UI가 관찰하는 단일 StateFlow<UiState>를 노출합니다. ViewModelContext나 안드로이드 생명주기 타입을 직접 참조하지 않아야 합니다. 이 점은 면접에서 자주 확인되는 부분이므로 반드시 기억해 두시기 바랍니다. Toast와 같은 일시적 UI 효과가 필요한 경우, ViewModel은 해당 효과를 UI 상태의 일부로 모델링하고 실제 사이드 이펙트 수행은 컴포저블에 맡깁니다.

Data Layer는 모든 데이터 연산을 관리하며, 데이터 소싱과 관련된 비즈니스 로직을 포함합니다. 핵심 추상화는 리포지토리(Repository)입니다. NewsRepositorygetArticlesStream(): Flow<List<Article>>이나 suspend fun toggleBookmark(articleId: String)과 같은 통합 API를 제공합니다. 내부적으로는 원격 데이터 소스(Retrofit 서비스)와 로컬 데이터 소스(Room DAO) 사이의 조율을 담당합니다. ViewModel은 Retrofit 서비스나 Room DAO에 직접 접근하지 않으며, 이러한 경계 덕분에 Retrofit을 Ktor로 교체하거나 Room을 SQLDelight로 변경하더라도 리포지토리와 데이터 소스 클래스 내부만 수정하면 됩니다. 이는 곧 Data Layer의 내부 구현이 상위 계층에 영향을 주지 않는다는 것을 의미합니다.

Domain Layer는 선택적 계층으로, UseCase(또는 Interactor) 클래스를 포함합니다. UseCase는 여러 리포지토리의 데이터를 결합하거나 복잡한 변환 규칙을 적용하는 단일 비즈니스 로직을 캡슐화합니다. 뉴스 앱을 예로 들면, GetPersonalizedFeedUseCaseNewsRepositoryUserPreferencesRepository를 결합하여 기사를 필터링, 정렬, 랭킹하는 역할을 수행할 수 있습니다. Domain Layer는 동일한 로직이 여러 ViewModel에서 필요하거나, ViewModelinit 블록에 복잡한 Flow 결합 로직이 누적되어 상태 홀더 본연의 역할이 흐려질 때 도입하는 것이 적절합니다.

단방향 데이터 흐름과 UI 상태 모델링

단방향 데이터 흐름(Unidirectional Data Flow, UDF)은 상태는 ViewModel에서 UI로 아래 방향으로 흐르고, 이벤트는 UI에서 ViewModel로 위 방향으로 흐른다는 원칙입니다. 뉴스 피드 화면에서 북마크 동작을 예시로 살펴보겠습니다.

  1. 사용자가 북마크 아이콘을 탭합니다. 컴포저블은 어떤 상태도 직접 변경하지 않으며, ViewModel에서 전달받은 onBookmarkClicked(articleId) 형태의 람다를 호출합니다.
  2. ViewModel은 이벤트를 수신하고 리포지토리에 위임합니다. viewModelScope.launch { newsRepository.toggleBookmark(articleId) }를 실행합니다.
  3. 리포지토리가 Room 데이터베이스를 업데이트합니다. ViewModel은 이미 Room의 Flow를 관찰하고 있으므로, 데이터베이스 변경이 북마크 플래그가 갱신된 새로운 기사 목록을 방출합니다.
  4. ViewModel이 새로운 목록을 수신하여 Success 상태로 래핑하고 StateFlow를 갱신합니다. Compose는 상태 변경을 감지하여 해당 항목을 리컴포지션합니다.

UI 상태 자체는 sealed interface로 모델링하여, 모든 화면 조건을 명시적이고 빠짐없이 표현합니다.

sealed interface NewsFeedUiState {
    data object Loading : NewsFeedUiState
    data class Success(
        val articles: List<Article>,
        val userMessage: String? = null
    ) : NewsFeedUiState
    data class Error(val message: String) : NewsFeedUiState
}

이 면접 질문은 구독자 전용입니다

Dove Letter를 구독하시면 안드로이드, 코틀린 개발 관련 독점 면접 질문의 전체 내용을 볼 수 있습니다.

구독하기
면접 질문 목록으로 가기