이동식 저장소

Android Architecture Layers - 1. Data 본문

Primary/Android

Android Architecture Layers - 1. Data

해스끼 2022. 6. 15. 10:12

다음 문서를 요약한 글입니다. 영문 버전으로 일독을 권합니다.

 

데이터 영역  |  Android 개발자  |  Android Developers

데이터 영역 UI 레이어에는 UI 관련 상태 및 UI 로직이 포함되지만 데이터 영역에는 애플리케이션 데이터 및 비즈니스 로직이 포함됩니다. 비즈니스 로직은 앱에 가치를 부여하는 요소로, 애플리

developer.android.com


Data 레이어는 앱에서 사용하는 데이터와 비즈니스 로직을 포함한다. 비즈니스 로직이란 데이터를 가공하여 앱에서 사용할 형태로 제공하는 코드를 말한다. 앱의 존재 의의를 결정하는 레이어이기도 하다. UI로만 설명되는 앱은 거의 없기 때문이다.

 

Data 레이어는 UI를 포함하여 앱의 다른 부분에서 사용될 데이터를 제공한다. 따라서 외부로 드러낼 인터페이스를 최대한 작성해야 한다. 다른 모듈 입장에서 직관적으로 사용할 수 있어야 한다는 뜻이다. 다른 모듈에 의존하지 않도록 작성하면 재사용성을 높일 수 있다.

 

데이터만 잘 구성해 놓으면 절반 이상은 성공한 것이다. UI에서 그대로 보여주기만 하면 되기 때문이다. 앱의 특성에 맞는 데이터 구조를 고민해 보자.

구성 요소

Data 레이어는 0개 이상의 data source를 포함하는 repository로 구성된다.

 

데이터의 종류별로 repository를 만들어야 한다. 예를 들어 ``ArticleRepository``와 ``UserRepository``는 별개의 repository로 작성되어야 한다.

Data source

Data source는 단 하나의 원천(파일, 데이터베이스, 네트워크 등)으로부터 데이터를 가져와야 한다. 외부의 데이터 시스템과 앱을 연결하는 역할을 한다.

Repository

Repository는 데이터를 0개 이상의 source로부터 받아 그 데이터의 유일한 원천(single source of truth)이 된다. Data source에게 받은 데이터를 그대로 반환해도 되고, 비즈니스 로직을 적용해도 된다.

 

다른 레이어에서 data source에 직접 접근하면 안 된다. Data source는 철저히 숨겨져야 하며, 오직 Repository를 통해서만 데이터에 접근할 수 있다. Data 레이어에서 얻은 데이터는 여러 스레드에서 동시에 사용할 수 있어야 하며, 불변해야 한다. 정확히는 데이터 객체를 직접 수정할 수 없어야 한다

 

Repository에서 data source에 접근하는 방법 중 dependency injection 기법을 사용하면 좋다. 필요한 객체를 직접 만드는 게 아니라 생성자 매개변수로 받는 것이다.

class SomeRepository(
    private val someLocalDataSource: SomeLocalDataSource
) { ... }

인터페이스 제공

일반적으로 Repository는 데이터를 Create, Read, Update, Delete(CRUD)하는 일회성 함수와 데이터가 바뀔 때마다 알려주는 함수를 제공한다.

 

Kotlin에서 CRUD 함수는 보통 ``suspend`` 함수로 작성할 수 있다. 또는 RxJava 등을 이용하여 처리할 수도 있다.

 

데이터가 바뀔 때마다 알려주려면 Kotlin의 flow를 사용하면 된다. 또는 예전에 자주 사용되던 ``LiveData``이나 RxJava ``Observable`` 등을 사용할 수도 있다.

 

한 번의 연산으로 끝나는 작업이라면 ``suspend`` 함수를, 데이터를 지속적으로 받을 필요가 있다면 flow를 사용하면 좋다.

클래스 작명법

Repository의 이름은 데이터의 종류 + Repository로 지어 보자. ``NewsRepository``처럼.

 

Data source의 이름은 데이터의 종류 + 데이터의 위치 + DataSource로 지을 수 있다.  예를 들어 ``NewsRemoteDataSource``로 지을 수 있다. 데이터의 위치를 구체적으로 표현하고 싶다면 ``NewsNetworkDataSource``처럼 지을 수도 있다. 

 

단, ``NewsSQLiteDataSource``처럼 구현 방법을 이름에 명시하지는 말자. Repository는 data source가 어떻게 구현되었는지 몰라야 하기 때문이다. 나라면 ``NewsLocalDataSource``라는 이름을 지을 것이다. 이름에 구현을 명시하지 않음으로써, 나중에 SQLite를 Room으로 교체하더라도 외부 클래스를 변경할 필요가 없어진다.

Repository 계층 쌓기

복잡한 비즈니스 로직을 하나의 Repository에서 구현하기 어려울 수도 있다. 예를 들어 User와 Article 정보를 하나로 합쳐서 제공해야 한다면 어떻게 해야 할까?

 

이런 경우에는 repository에 의존하는 또 다른 repository를 정의할 수 있다. 위의 그림처럼 사용자 인증 로직을 구현하기 위해 Login과 Registration 작업을 합쳐 UserRepository로 제공할 수 있다. 

스레드

Data source와 repository는 UI 스레드를 block하지 않아야 한다. 사실 Room이나 Retrofit 등 대부분의 라이브러리는 UI 스레드를 막지 않는다. 데이터 함수를 직접 작성한다면 메인 스레드를 block하지 않도록 작성하자.

Lifecycle

Data source 또는 Repository는 앱 전체에서 사용될 수도 있고, 특정 화면에서만 사용될 수도 있다. 중요한 점은 객체가 사용되는 범위를 파악하여 적절한 scoping 정보를 제공해야 한다는 것이다. Activity, Fragment, WorkManager 등등..

 

특히 DI 모듈을 만들 때 주의하자. 모든 객체를 @Singleton으로 매핑하면 당장은 편할 지 몰라도 코드가 논리적으로 잘못 scope되고, 코드를 읽는 사람이 객체의 사용 범위를 잘못 판단하게 될 수도 있다.

데이터 거르기

앱에서 사용하는 데이터와 Data source가 제공하는 데이터가 다른 경우도 있다. 예를 들어 프로필 화면에서는 사용자의 이름, 이메일만 사용해도 된다.

 

이런 경우에는 data source와 앱의 나머지 부분에서 사용할 데이터 객체를 분리하는 것이 좋다. 사용하지 않을 데이터까지 굳이 넘겨줄 필요는 없다. 아니 넘겨주지 않는 편이 좋다. 겸사겸사 메모리도 절약하고.

 

어쨌든, 앱의 서로 다른 두 부분에서 사용하는 데이터가 다르다면 별개의 데이터 모델을 만들어 보자. 물론 어느 객체를 어디서 사용하는지 잘 기억해야 한다.

구현 예시

맨 위에 링크한 공식 문서의 Common tasks 문단을 참고하자.

Comments