본문 바로가기

안드로이드

안드로이드 Repository, Data Layer 디자인 패턴

Repopsitory 패턴
Repository 패턴은 앱으로부터 데이터 계층을 분리시켜주는 디자인 패턴이다. (여러 블로그에서 캡슐화 시켜준다고도 표현했다.)
데이터 레이어는 UI와 별도로 앱의 데이터와 비즈니스 로직을 처리하는 앱 부분을 나타낸다.
데이터에 접근할 수 있도록 일관된 API를 제공해준다.
UI가 사용자에게 정보를 제공하는 동안 데이터 레이어에는 네트워킹 코드, Room DB, 오류처리, 데이터를 조작하는 등의 코드가 포함된다.

저장소는 데이터 소스(예: 영구 모델, 웹 서비스, 캐시) 간의 충돌을 해결하고 이 데이터의 변경사항을 중앙 집중화할 수 있다.

저장소는 데이터 소스(예: 영구 모델, 웹 서비스, 캐시) 간의 충돌을 해결하고 이 데이터의 변경사항을 중앙 집중화할 수 있다.

repository 를 구현하기 위해서는 ViedeoRepository와 같은 분리된 클래스를 사용하한다.
repository 클래스는 data sources 와 앱을 분리해주고 앱의 나머지 부분에 대한 데이터 액세스를 위한 깔끔한 API를 제공한다.
repository를 썼을때의 이점
저장소 모듈은 데이터 작업을 처리하고 여러 백엔드 사용을 허용한다.
실제 세상에서 앱에서는 repository가 네트워크를 통해서 데이터를 불러올지 로컬 db에 저장되있던 데이터를 사용할지 결정한다. (영문도 한글도 솔직히 잘 이해가 안된다. 아마 문맥상 repository 패턴을 사용하지 않았을때 단점을 얘기하려고 한듯 하다.)
repository 패턴을 사용하면 뷰 모델과 같은 호출 코드에 영향을 주지 않고 다른 지속성 라이브러리로의 이전과 같은 구현 세부정보를 교체할 수 있다.
이는 코드를 모듈화 시켜주고 테스트하기 용이하게 만드러준다.
문장이 길었는데 결국 repository 패턴을 이용하면 코드를 모듈화 시켜주고 테스트하기 편하게 해준다는 장점이 있다는 말이다.
repository는 앱의 데이터를 최신 상태로 유지해줘야 한다. 네트워크 리소스와 오프라인 캐시 등 여러 데이터소스로 작업할 때  repository 는 앱의 데이터를 최대한 정확하고 최신 상태로 유지해야한다.
캐싱
캐시는 앱에서 사용하는 데이터 저장를 나타낸다.
갑자기 네트워크가 끊기면 앱은 더이상 새로운 데이터를 받아올 수 없는데 마지막으로 받아온 데이터를 사용해서 앱이 유지될 수 있다. (캐시된 데이터로 대체 가능)
아래 예시들은 안드로이드에서 네트워크 캐싱을 구현하는 몇가지 방법이다.

위의 respository 관련 개념은 아래 안드로이드 Repository Pattern 문서를 분석하면서 작성했다.

 

Data layer
Repository 클래스들은 아래 작업에 대한 책임이 있다.
  • 앱 전반에 필요한 데이터를 전달해준다.
  • 데이터 변경의 중앙화
  • 여러 데이터 소스 사이에서 복잡함을 해소해줌
  • 앱 전반에서 데이터를 추상화 시켜줌 (이게 아마 인터페이스 같은 걸로 데이터 갖고오는 것을 얘기하는듯 Room DB만 봐도 답이 나오지)
  • 비즈니스 로직을 포함한다.
 
Each data source class should have the responsibility of working with only one source of data, which can be a file, a network source, or a local database. Data source classes are the bridge between the application and the system for data operations.
각 데이터 소스는 하나의 데이터 소스에서만 작업을 해야할 책임이 있다. (한놈당 하나의 일을 해야한다.)
이는 파일, 네트워크 소스, 로컬 db 등이 있다. 데이터 소스는 애플리케이션과 시스템 사이에서 연결 다리 역할을 해준다.
 
다른 layer 계층은 바로 data source에 접근해서는 안된다. data source 에 접근하는 계층은 반드시 repository 클래스여야 한다. repository 클래스를 엔트리 포인트로 사용하면 아키텍처의 여러 계층을 독립적으로 확장할 수 있습니다.
 
repository 계층에서 전송된 데이터는 반드시 immutable 해야한다. 그래서 다른 클래스에 의해서 간섭되서는 안된다.  간섭되게 되면 값을 일관성 없는 상태로 만들 위험이 있다. immutable 데이터는 멀티스레드 환경에서도 안전하게 데이터가 다뤄진다.
 
의존성 주입에 따라서, repository 는 data source를 생성자에서 받는다.
(처음 봤을땐 못봤는데, 아래 소괄호를 잘보자. 생성자에 DataSource 로 접근할 수 있게끔 되있다.
그리고 그렇게 공부했던 생성자를 이용한 DI 이다.)
 
class ExampleRepository(
    private val exampleRemoteDataSource: ExampleRemoteDataSource, // network
    private val exampleLocalDataSource: ExampleLocalDataSource // database
) { /* ... */ }

Expose APIs

data layer에 있는 클래스들은 보통 CRUD작업이나 데이터가 실시간으로 변경되는 것을 알려주기 위한 메소드들을 보여준다.(노출한다.)
(Dao 객체로 RoomDB의 데이터를 CRUD하는 작업등을 생각하면 이해가 쉬울 것이다.)
One-shot operations  데이터 계층은 코틀린의 suspend 함수를 expose 해야 한다. 자바에서는 데이터 계층은 콜백이나 결과를 알려주는 callback 이 있는 Single, Maybe, or Completable 를 expose 해야 한다.
(그러니까 CRUD 작업은 코틀린은 코루틴 자바는 위에 언급한 기술들을 써야한다고 얘기하네. Executor 를 써도 된다. GPT의 언급에 의하면 Google에서도 밀어주는 방법이 아니고 효율적인 방법은 아니라고 말하지만 말이다.)
 
To be notified of data changes over time: 데이터 계층은 코틀린의 flow에 의해서 계속해서 expose 되야 한다. 자바개발에서는 callback 을 활용해서 새로운 데이터를 callback에서 expose 해줘야 하고 RxJava 나 Observablae 또는 Flowable 를 쓸 수 있다.
 
class ExampleRepository(
    private val exampleRemoteDataSource: ExampleRemoteDataSource, // network
    private val exampleLocalDataSource: ExampleLocalDataSource // database
) {

    val data: Flow<Example> = ...

    suspend fun modifyData(example: Example) { ... }
}

Make a network request

네트워크 요청을 만드는 것은 안드로이드 앱에서 가장 일반적인 작업중 하나다.
뉴스앱은 사용자에게 가장 최신 데이터를 알려줘야할 필요가 있다.
그러므로 앱에는 네트워크 작업을 관리하기 위한 데이터 소스 클래스가 필요합니다: NewsRemoteDataSource.
app전반에 정보를 보내기 위해서, news data를 관리하는 repository가 새로 만들어진다. : NewsRepository
사용자가 화면을 키면 뉴스는 항상 새로운 데이터를 보여줘야한다. 그러므로, 이는 UI기반 작업이다.

Create the data source (데이터 소스를 만드는 방법이다.)

가장 뉴스의 최신 정보 데이터를 보여줘야한다. : a list of ArticleHeadline 객체다.
데이터 소스는 main-safe한 방법으로 최신 정보를 네트워크에서 제공해줘야 한다.
이를 위해서는 CoroutineDispatcher 나 Executor 를 사용해서 task를 실행해야 한다.
 
class NewsRemoteDataSource(
  private val newsApi: NewsApi,
  private val ioDispatcher: CoroutineDispatcher
) {
    /**
     * Fetches the latest news from the network and returns the result.
     * This executes on an IO-optimized thread pool, the function is main-safe.
     */
    suspend fun fetchLatestNews(): List<ArticleHeadline> =
        // Move the execution to an IO-optimized thread since the ApiService
        // doesn't support coroutines and makes synchronous requests.
        withContext(ioDispatcher) {
            newsApi.fetchLatestNews()
        }
    }

// Makes news-related network synchronous requests.
interface NewsApi {
    fun fetchLatestNews(): List<ArticleHeadline>
}
 
NewsApi 인터페이스는 network API client의 구현체를 숨겨준다. 백엔드 작업을 해주는 인터페이스가 Retrofit인지 HttpURLConenction 인지 가리지 않는다.
인터페이스를 API를 구현해놓으면 앱 안에서 얼마든지 바꿔서 사용하는게 가능하다. (리스코프 치환원칙)
 
게다가 의존성을 바꾸거나 테스트하기 용이하고 테스트용 구현체를 넣어서 테스트하기도 편하다.

Create the repository

이 작업을 위해 리포지토리 클래스에는 추가 로직이 필요하지 않으므로 NewsRepository는 네트워크 데이터 소스의 프록시 역할을 합니다. 이 추상화 계층을 추가하는 것의 이점은 인메모리 캐싱 섹션에서 설명합니다.
 
// NewsRepository is consumed from other layers of the hierarchy.
class NewsRepository(
    private val newsRemoteDataSource: NewsRemoteDataSource
) {
    suspend fun fetchLatestNews(): List<ArticleHeadline> =
        newsRemoteDataSource.fetchLatestNews()
}
To learn how to consume the repository class directly from the UI layer, see the UI layer guide.
 
출처

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

'안드로이드' 카테고리의 다른 글

DiffUtil이란?  (0) 2023.11.17
리사이클러뷰 데이터가 꼬일때 해결하는 방법  (2) 2023.11.15
Handler, Looper 핸들러와 루퍼의 의미  (0) 2023.10.16
Handler, Looper, Message  (0) 2023.10.13
Dependncy injection  (0) 2023.10.09