안드로이드 모듈 설계

Pyro·2022년 2월 8일
0

파이로가 아래 설계대로 구현한 참고 코드

요약

핵심 개념

  • UI
    • View
    • ViewModel
  • domain
    • UseCase
    • Repository Interface
    • Model
  • data
    • Repository Implementation
    • DataSource
    • DAO
    • DTO, Entity

의존성 흐름 (Dependency Flow)

모듈간 import 하는 방향은 다음과 같다.
(의존성 역전을 도입했다.)

UI -> domain <- data

실행 흐름 (Control Flow)

코드는 다음과 같은 순서로 실행된다.
(함수가 호출되는 순서라고 생각해도 좋다.)

View
-> ViewModel
-> UseCase(ui 와 domain 의 경계)
-> Repository(domain 과 data의 경계)
-> DataSource
-> DAO (앱과 앱 외부(서버, 로컬DB)의 경계)

데이터 변화 순서

서버의 API 로 JSON 요청 -> DTO -> Model -> ViewModel -> View 에게 전달
Local DB를 DAO로 조회 -> Entity -> Model -> ViewModel -> View 에게 전달

상세 설명

핵심 개념 설명

  • UI : 화면에 보여주기 위한, 안드로이드에 프레임워크에 의존적인 모듈이다.
  • domain : 비즈니스 로직을 가진, 순수 코틀린 모듈이다.
  • data : 데이터를 조회 및 조작하기 위해, 서버 혹은 로컬 DB 와 통신하는 모듈이다.

UI

View

앱 화면에 보여주기 위한 Activity, Fragment 등이 정의 되어 있다.

ViewModel

Model 의 비동기적인 변화에 대응하기 위한 LiveData 가 있다.

domain

UseCase

UseCase 란 사용자(User) 가 시스템 혹은 프로그램에게 요구할 수 있는 수행 작업(Case)의 최소 단위이다.
이해가 어렵다면, 최소 실행 단위로 쪼갠 데이터의 CRUD 작업 이라고 생각해도 좋다.
여기서 데이터에 작업하는 로직이란, 곧 비즈니스 로직이다.
비즈니스 로직은 Model 에 정의되어야 한다.

UseCase 를 수행(execute) 하기 위해서, 서버 API 가 필요할 수도 필요하지 않을 수도 있다.
사용자가 꼭 데이터와 관련된 작업을 요구하지 않을 수도 있기 때문이다.

반대로 외부 API 를 사용자가 호출하기 위해서는 반드시 UseCase 가 정의 되어야 한다.
서버 API 를 호출하는 것 자체가 사용자의 요청에 의해서 발생한 작업이기 때문이다.
사용자가 원하는 게 무엇인지 UseCase 를 정의하지 않고,
무작정 서버와 연동하는 로직부터 작성하는 것은 어불성설이다.

사용자는 UI 의 View 를 통해 시스템과 상호작용하기에,
각 View 는 자신이 할 수 있는 UseCase 를 알고 있어야 한다. (의존성이 있다.)

본래는 별도의 UseCase 객체를 정의해야하지만,
Repsitory Interface 에서의 메서드의 형태로 UseCase 를 간략화 했다.

Repository Interface

domain 에서 DataSource 에 접근하기 위한 추상화된 인터페이스를 Repository 라고 한다.
domain 은 자신이 받아온 data 가 서버 API 호출을 통해 가져온 것인지, 로컬 파일을 읽어온 것인지 알지 못한다.

어떤 DataSource 를 사용할지는 프레임워크에서 결정하여 의존성 주입(Dependency Injection) 한다.
의존성 주입을 위한 제어권을 프레임워크에게 넘겨주기 위하여, 추상화된 인터페이스인 repository 가 필요하다.
의존성 주입을 할 수 있는 Repository 의 실질적인 구현체는 data 모듈에서 구현하도록 한다.

Model

핵심적인 비즈니스 로직을 가진 진정한 의미의 "객체"가 Model 이다.
조영호 님의 "객체지향의 사실과 오해" 책에 나오는 "역할, 책임, 협력"을 하는 객체가 정의되어 있다.
순수 코틀린 객체이므로 단위 테스트가 가능하다.

비즈니스 로직의 예시로는 Verification 과 Validation 같은 것들이 있다.

data

Repository Implementation

domain 에서 정의된 Repository 인터페이스의 구현체이다.
DataSource 에서 넘겨준 DTO 혹은 Entity 자료구조를 순수 코틀린 객체인 Model 로 변환해서
domain 에게 넘겨주어야 한다.

DataSource

DAO(Data Access Object) 를 의존성 주입 받아서, Repository 에게 데이터를 제공하는 데이터의 원천이다.
DataSource 는 자신이 서버에서 데이터를 받아올지, 아니면 로컬 파일을 읽어올지에 따라서,
RemoteDataSource 가 되거나, LocalDataSource 라고 이름이 지어진다.

DAO (Data Access Object)

서버 혹은 로컬 DB 와 같은 앱 외부에서 데이터와 상호작용할 수 있는 추상화된 인터페이스이다.
외부에서 가져온 JSON 혹은 하드 텍스트를 프로그래밍 언어로 다루기 편한 인스턴스로 변환을 해준다.

서버에서 데이터를 가져와서 만들어진 인스턴스의 경우 DTO 라는 표현을 사용하고,
로컬 DB 에서 데이터를 가져와서 만들어진 인스턴스의 경우 Entity 이라는 표현을 사용한다.

서버와 통신할 DAO 는 Retrofit 을 사용하고,
로컬 DB 에 접근하는 DAO 는 안드로이드 Room 을 사용한다.

원래 DataSource 와 DAO 는 분리되어야 하지만,
구현을 하다가 하나로 합치게 되었다.

DTO

Data Transfer Object 이다.
외부에서 제공되는 데이터를 프로그램에서 다룰 수 있는 인스턴스로 변환한 것이다.
어떤 데이터가 제공될지 모르므로, 모든 데이터 타입을 NULL 이 허용되는 Optional 로 해야한다.

초기의 DTO 에서 데이터 타입을 전부 Optional 로 선언한 이유는 앱이 죽는일만큼은 무슨일이 있어도 막아야 하기 때문이다.

DTO 에서 Optional 로 처리된 데이터는 Model 로 변환됨으로써 실제 의미를 가지는 데이터가 된다.

DTO 는 API 에 종속적이어야 한다.
즉 API 가 달라지면, 그 API 의 Request 와 Response DTO 를 새로 정의해야한다.
절대로 형태가 비슷하다고, 여러 API에 겹쳐서 DTO를 재사용하지 말자.

Entity

SQLite 로컬 DB 를 사용하기 위해서 식별자(Primary Key)를 반드시 가져야 하는 인스턴스이다.
안드로이드 Room 사용을 위해서 Entity 어노테이션을 붙인 데이터 클래스들이다.

실행 흐름 설명

함수가 다음과 같은 흐름으로 호출된다.

View 에서 API 호출을 하고 싶음 (Model 이 필요)

  • -> ViewModel.fetch()
    • Model 의 비동기적인 변화를 observe 할 수 있도록 감쌈 (Observer 패턴)
  • -> FetchUseCase.execute()
    • ui 모듈과 domain 모듈 사이의 UseCase에서 정의된 함수
    • Model 을 통해 비즈니스 로직을 수행
  • -> Repository.fetch()
    • data 모듈과 domain 모듈 사이의 Repository 인터페이스의 추상 함수
    • DTO 에서 NULL 을 제거하고 비즈니스 로직을 가진 Model 로 변환
  • -> DataSource.fetch()
    • NULL 투성이 ResponseDTO 를 반환
  • -> Dao.fetch()
    • 서버 API 호출을 통해 json 과 같은 하드 텍스트 데이터를 가져옴
    • 서버와 안드로이드 사이의 추상화된 인터페이스

개념들 사이의 관계

  • 1 대 1 관계: 서버 API , Dao 함수 , UseCase , DTO
  • n 대 m 관계: domain , DTO
  • n 대 m 관계: domain , ViewModel

서버 API 를 호출하기 위해 DAO 함수가 필요하고,
DAO 함수는 API 에 종속적인 DTO 를 반환해야하며,
UseCase 를 통해 Dao 함수와 상호작용할 수 있다.

반면,
domain 에 있는 Model 은
DTO 와 1대1 관계가 아니다.
즉 하나의 domain 객체를 만드는데 여러개의 API 를 호출해서 정보를 가져와야할 수도 있고,
하나의 API 를 호출해서 여러개의 domain 객체를 만들 수도 있다.

마찬가지로 하나의 ViewModel 을 만드는데 여러개의 domain 객체가 필요할 수도 있고,
하나의 domain 객체로 여러개의 ViewModel 을 만들 수도 있다.

Reference

Android Data Layer

Clean Architecture

Why Do We need Clean Architecture

AAC(Android Architecture Components)

앱 아키텍처 가이드

profile
dreams of chronic and sustained passion

0개의 댓글