안드로이드 앱 클린 아키텍처 - 데이터 레이어 - 1

이윤설·2024년 9월 22일
0

안드로이드 연구소

목록 보기
4/33

데이터 레이어

UI 레이어는 UI 관련 상태와 로직을 포함하지만, 데이터 레이어는 애플리케이션 데이터와 비즈니스 로직을 담당한다. 비즈니스 로직은 앱의 핵심 가치를 결정하는 규칙으로, 데이터의 생성, 저장, 변경 방식을 정의한다.

데이터 레이어의 장점

관심사를 분리함으로써 데이터 레이어는 여러 화면에서 재사용이 가능하고, 애플리케이션의 다양한 부분에서 데이터를 공유할 수 있다. 또한, 비즈니스 로직을 UI 외부에서 테스트하기 쉬워지며, 애플리케이션의 확장성도 높아진다.

데이터 레이어의 아키텍처

데이터 레이어는 하나 이상의 데이터 소스(예: 네트워크, 로컬 데이터베이스)를 포함하는 저장소(Repository)로 구성된다. 데이터 유형에 따라 별도의 저장소 클래스를 만들어야 하며, 각 저장소 클래스는 다음과 같은 역할을 한다:

  • 앱의 나머지 부분에 데이터를 제공
  • 여러 데이터 소스 간의 충돌 해결
  • 비즈니스 로직 포함
데이터 소스와 저장소

각 데이터 소스 클래스는 한 가지 데이터 소스(예: 파일, 네트워크, 로컬 데이터베이스)만 사용해야 하며, 다른 계층에서는 데이터 소스에 직접 접근하지 말아야 한다. 항상 저장소 클래스를 통해 데이터에 접근해야 하며, 이를 통해 데이터의 일관성과 안전성을 보장할 수 있다.

불변성

이 레이어에서 노출된 데이터는 변경 불가능해야 한다. 그래야 값을 일관되지 않은 상태로 만들 위험이 있는 다른 클래스에 의한 조작이 불가능해진다. 또한 변경 불가능한 데이터는 여러 스레드에서 안전하게 처리될 수 있다.

API 노출

데이터 레이어에서는 CRUD(생성, 조회, 수정, 삭제) 작업을 처리하며, 시간 경과에 따른 데이터 변경 사항을 처리할 수 있다. 원샷 작업은 코틀린의 정지 함수를 사용해 노출하고, 지속적으로 변경되는 데이터는 Flow 또는 RxJava를 통해 노출할 수 있다.

class ExampleRepository(
    private val exampleRemoteDataSource: ExampleRemoteDataSource, // 네트워크
    private val exampleLocalDataSource: ExampleLocalDataSource // 데이터베이스
) {
    val data: Flow<Example> = ...
    suspend fun modifyData(example: Example) { ... }
}

복잡한 저장소 구조

비즈니스 요구사항이 복잡한 경우, 저장소가 다른 저장소에 의존할 수 있다. 예를 들어 UserRepositoryLoginRepository, RegistrationRepository에 의존할 수 있다. 이는 책임을 분리하고 재사용성을 높이는 데 도움이 된다.

정보 소스

각 저장소는 일관된 정보 소스를 제공해야 한다. 정보 소스는 데이터베이스나 메모리 내 캐시일 수 있으며, 저장소는 다양한 데이터 소스를 결합하고 충돌을 해결하여 최신 데이터를 제공해야 한다.

오프라인 우선 지원을 제공하려면 데이터베이스와 같은 로컬 데이터 소스를 정보 소스로 사용하는 것이 좋다.

스레딩

데이터 소스와 저장소 호출은 기본적으로 스레드 안전성이 보장되어야 한다. 장기 실행 작업은 적절한 스레드로 전환해 처리해야 하며, Room이나 Retrofit과 같은 API를 사용해 스레드 안전성을 확보할 수 있다.

수명 주기

데이터 레이어의 클래스는 참조되는 동안 메모리에 유지되며, 애플리케이션 또는 특정 흐름의 수명 주기에 따라 인스턴스의 범위를 지정할 수 있다. 중요한 클래스는 애플리케이션 전체에서 재사용되도록 Application 클래스의 범위를 따르게 설정할 수 있다.

종속 항목 관리와 범위 지정은 데이터 레이어의 인스턴스를 관리하는 중요한 요소이며, Hilt와 같은 종속성 주입 도구를 활용해 이를 쉽게 처리할 수 있다.

물론이다. 내용을 가독성 좋게 정리해보겠다.


대표 비즈니스 모델

데이터 레이어

데이터 레이어에서 노출하는 데이터 모델은 다양한 데이터 소스에서 가져오는 정보의 하위 집합일 수 있다. 네트워크와 로컬의 여러 데이터 소스는 애플리케이션에 필요한 정보만 반환하는 것이 이상적이지만, 실제로는 그렇지 않은 경우가 많다.

예를 들어, 뉴스 API 서버는 기사 정보뿐만 아니라 수정 기록, 사용자 댓글, 메타데이터 등을 반환할 수 있다. 이 경우, 화면에 기사 콘텐츠와 작성자 기본 정보만 표시되므로, 앱은 기사에 대한 많은 정보를 필요로 하지 않는다. 따라서 모델 클래스를 분리하고 저장소에서 필요한 데이터만 노출하도록 하는 것이 좋다.

모델 클래스 예시

data class ArticleApiModel(
    val id: Long,
    val title: String,
    val content: String,
    val publicationDate: Date,
    val modifications: Array<ArticleApiModel>,
    val comments: Array<CommentApiModel>,
    val lastModificationDate: Date,
    val authorId: Long,
    val authorName: String,
    val authorDateOfBirth: Date,
    val readTimeMin: Int
)

data class Article(
    val id: Long,
    val title: String,
    val content: String,
    val publicationDate: Date,
    val authorName: String,
    val readTimeMin: Int
)

모델 클래스를 분리함으로써 얻는 이점은 다음과 같다:

  • 필요한 데이터만 사용하여 메모리를 절약할 수 있다.
  • 외부 데이터 유형을 앱에서 사용하는 데이터 유형에 맞게 조정할 수 있다.
  • 관심사를 분리하여 대규모 팀이 각 기능의 네트워크 레이어와 UI 레이어에서 독립적으로 작업할 수 있다.

데이터 작업 유형

데이터 레이어에서 작업의 중요도에 따라 다양한 유형으로 나눌 수 있다.

  1. UI 지향 작업: 사용자가 특정 화면에 있을 때만 관련된 작업. 화면을 벗어나면 취소된다. 예: 데이터베이스에서 일부 데이터를 표시하는 작업.

  2. 앱 지향 작업: 앱이 열려 있는 동안 관련된 작업. 앱이 종료되면 취소된다. 예: 네트워크 요청 결과를 캐시하는 작업.

  3. 비즈니스 지향 작업: 취소할 수 없는 작업. 프로세스 종료 후에도 유지된다. 예: 프로필에 사진 업로드.

일반적인 작업

네트워크 요청

네트워크 요청은 Android 앱에서 가장 일반적인 작업이다. 예를 들어, 뉴스 앱은 최신 뉴스를 네트워크에서 가져와 사용자에게 표시해야 한다. 이를 위해 NewsRemoteDataSource 클래스를 만들어 네트워크 작업을 관리한다.

class NewsRemoteDataSource(
    private val newsApi: NewsApi,
    private val ioDispatcher: CoroutineDispatcher
) {
    suspend fun fetchLatestNews(): List<ArticleHeadline> =
        withContext(ioDispatcher) {
            newsApi.fetchLatestNews()
        }
}

interface NewsApi {
    fun fetchLatestNews(): List<ArticleHeadline>
}

저장소 만들기

NewsRepository 클래스는 네트워크 데이터 소스의 프록시 역할을 하며, 추가 로직이 필요 없는 경우 그대로 사용한다.

class NewsRepository(
    private val newsRemoteDataSource: NewsRemoteDataSource
) {
    suspend fun fetchLatestNews(): List<ArticleHeadline> =
        newsRemoteDataSource.fetchLatestNews()
}

메모리 내 데이터 캐싱 구현

뉴스 앱에 새로운 요구사항이 추가되어, 사용자가 화면을 열 때 캐시된 뉴스를 표시해야 한다. 이를 위해 메모리 내 데이터 캐싱을 추가한다.

class NewsRepository(
    private val newsRemoteDataSource: NewsRemoteDataSource
) {
    private val latestNewsMutex = Mutex()
    private var latestNews: List<ArticleHeadline> = emptyList()

    suspend fun getLatestNews(refresh: Boolean = false): List<ArticleHeadline> {
        if (refresh || latestNews.isEmpty()) {
            val networkResult = newsRemoteDataSource.fetchLatestNews()
            latestNewsMutex.withLock {
                latestNews = networkResult
            }
        }
        return latestNewsMutex.withLock { latestNews }
    }
}

데이터 저장 및 디스크에서 가져오기

북마크한 뉴스와 사용자 환경설정과 같은 데이터는 프로세스 종료 후에도 남아 있어야 하므로, 데이터를 디스크에 저장해야 한다. 이를 위해 Room 데이터베이스 또는 DataStore를 사용할 수 있다.

  • Room 데이터베이스: 쿼리하거나 참조 무결성이 필요한 대규모 데이터 세트에 적합.
  • DataStore: 작은 데이터 세트, 예를 들어 사용자 환경설정에 적합.
  • 파일 스토리지: JSON 객체와 같은 큰 객체에 적합.

https://developer.android.com/topic/architecture/data-layer?hl=ko&_gl=1*fgjepl*_up*MQ..*_ga*NjU2MDc3Ni4xNzI3MDA0NDQ5*_ga_6HH9YJMN9M*MTcyNzAwOTUzMC4yLjAuMTcyNzAwOTUzMC4wLjAuODQyNjQ0NDgy

profile
화려한 외면이 아닌 단단한 내면

0개의 댓글

관련 채용 정보