안드로이드 연구소

[안드로이드 클린 아키텍처] 안드로이드 앱 아키텍처 가이드라인(Domain레이어) 본문

안드로이드 연구소/클린아키텍처

[안드로이드 클린 아키텍처] 안드로이드 앱 아키텍처 가이드라인(Domain레이어)

안드로이드 연구원 2023. 6. 16. 17:53

Q1. Domain레이어에 대해 설명해줘

Domain레이어는 UI 계층과 데이터 계층 사이에 있는 계층입니다.

복잡한 비즈니스 로직 또는 여러 ViewModel에서 재사용되는 단순한 비즈니스 로직을 캡슐화하는 역할을 합니다.
복잡성을 처리하거나 재사용성을 선호하는 경우와 같이 필요한 경우에만 사용해야 합니다.

일반적으로 Data레이어에서 재사용성 또는 캐싱을 장려하는 것은 복잡한 프로세스 발생합니다.

Domain레이어의 목적은 Data레이어에서 사용한 데이터를 재사용하기 위해서인데요

Data레이어에서도 캐싱 기술을 이용해서 재사용을 할 수 있지만 새로운 기술 학습과 복잡한 프로세스로 사용하기가 많이 어려웠습니다.

그래서 Data레이어에서보다 쉽게 Domain레이어에서 데이터를 재사용할 수 있게하고 있는 것으로 보입니다.

 

 

Q1-1. Domain레이어는 구성요소에 대해서 알려줘

이를 위해서 Domain레이어에서는 UseCase 클래스가 사용됩니다.
각 UseCase는 단일 기능에 대해서만 책임을 져야 합니다.

그렇다면 아래 구조가 바로 안드로이드 클린아키텍처의 클래스 구조가 되겠네요.

-Data레이어 - DataSource < Repository

-Domain레이어 - UseCase 

-UI레이어 - ViewModel < UI element

 

 

Q1-2. UseCase는 어떤 클래스들을 의존성으로 가질 수 있어?

즉, UseCase는 일반적으로 레포지토리 클래스에 의존하며 또한 다른 UseCase 클래스 또한 포함할 수 있다.
뿐만 아니라 UseCase는 레포지토리 클래스 뿐만 아니라 다른 UseCase클래스도 의존할 수 있다.
레포지토리와 동일한 방식으로 코루틴(Kotlin의 경우)을 사용하여 UI 계층과 통신합니다.
class GetLatestNewsWithAuthorsUseCase(
  private val newsRepository: NewsRepository, // 레파지토리
  private val authorsRepository: AuthorsRepository, // 레파지토리
  private val formatDateUseCase: FormatDateUseCase // UseCase
) { /* ... */ }

UseCase에서는 Data레이어에서 생성한 레포지토리를 의존해서 사용해야하며

ViewModel에서는 UseCase를 의존해서 사용할 것 같은 조심스런 예측을 해봅니다.

 

 

Q1-3. UseCase를 어떻게 인스턴스로 사용할 수 있지?

UseCase클래스에서 invoke() 연산자를 사용하면
UseCase를 인스턴스로 호출하여 사용할 수 도 있습니다.
class FormatDateUseCase(userRepository: UserRepository) {

    private val formatter = SimpleDateFormat(
        userRepository.getPreferredDateFormat(),
        userRepository.getPreferredLocale()
    )

    // invoke를 사용하여 UseCase를 함수처럼 사용하기
    operator fun invoke(date: Date): String {
        return formatter.format(date)
    }
}
class MyViewModel(formatDateUseCase: FormatDateUseCase) : ViewModel() {
    init {
        val today = Calendar.getInstance()
        val todaysDate = formatDateUseCase(today)
        /* ... */
    }
}

 

 

Q1-4. UseCase는 실제 사용 예제를 보여줘

/**
 * This use case fetches the latest news and the associated author.
 */
class GetLatestNewsWithAuthorsUseCase(
  private val newsRepository: NewsRepository, // 레파지토리 의존성 주입
  private val authorsRepository: AuthorsRepository, // 레파지토리 의존성 주입
  private val defaultDispatcher: CoroutineDispatcher = Dispatchers.Default
) {
    // invoke()메서드로 호출
    suspend operator fun invoke(): List<ArticleWithAuthor> =

       // UseCase 또한 비동기에 실행
       withContext(defaultDispatcher) {
            val news = newsRepository.fetchLatestNews() // news 레파지토리 데이터 호출
            val result: MutableList<ArticleWithAuthor> = mutableListOf()
            
            for (article in news) {
                // authors레파지토리에서 데이터 호출
                val author = authorsRepository.getAuthor(article.authorId)
                result.add(ArticleWithAuthor(article, author))
            }
            result
        }
}

 

그렇다면 UI레이어의 ViewModel에서 위의 UseCase를 사용한다면 아래와 같겠네요.

class NewsViewModel(
    private val getLatestNewsWithAuthorsUseCase: GetLatestNewsWithAuthorsUseCase
) : ViewModel() {

    private val _uiState = MutableLiveData<NewUiState>()
    val uiState: LiveData<NewUiState> = _uiState

    fun fetchLatestNews() {
        viewModelScope.launch {
            try {
                val newsItems = repository.newsItemsForCategory(category)
                _uiState.value = _uiState.value?.copy(newsItems = newsItems)
            } catch (e: Exception) {
                 val messages = getMessagesFromThrowable(ioe)
                _uiState.value = _uiState.value?.copy(userMessages = messages)
            }
        }
    }
}

 


클린 아키텍처가 어떤 것들이진 조사하고 공부한뒤 하나의 포스팅을 올리는 것만 일주일이 넘게 걸렸네요.

 

기본 클린 아키텍처 이론에서는 각 각 관심사가 다른 4개의 계층을 분리하여 외부에서 내부로 각 자를 종속한다는 것을 알았고

안드로이드 클린아키텍처에서는 3개의 계층으로 분리하여 가장 안 속에서는 Data레이어, 그 다음에는 Domain레이어, Ui레이어가 있었고

각 계층의 구성요소 클래스들과 서로 어떻게 의존하여 사용하는 법에 대해서 알게 되었습니다.

 

안드로이드 클린 아키텍처의 프로세스를 정리하면

1. [Data레이어] DataSource(서버나 데이터베이스로 연결 후 데이터 가져오기) ->
2. [Data레이어] Repository(DataSource데이터 호출 메서드) ->
3. [Domain레이어] UseCase(Repository를 통해 가져온 데이터 재사용) ->
4. [UI레이어] ViewModel의 UI state(UseCase의 데이터 호출 후 UI state로 저장) ->
5. [UI레이어] UI element(UI 업데이트)

 

안드로이드 클린 아키텍처를 정리해보면 

1. 기존 앱 아키텍처 패턴에서 ViewModel과 Repository 역할을 분리 
기존 Repository의 역할은 DataSource와 Repository와 UseCase가 각자의 관심사로 분리가 되었고
기존 ViewModel의 모든 데이터를 관리하는 역할에서 UI 관련 데이터를 관리하도록 세분화 되었다. 
2. 확실한 의존성 주입 사용
UI element -> ViewModel(UI state) -> UseCase -> Repository -> DataSource
이 구조로 의존성 주입을 하여 서로의 클래스가 미칠 영향을 최소화한다.
3. 관심사 분리로 인한 각 각의 클래스 파일 테스트 용이
DataSource 클래스는 서버나 데이터베이스와 연결이 잘 되는지?
Repository 클래스는 데이터 호출이 잘 되는지?
UseCase는 데이터가 재사용 가능한지?
ViewModel에서 UI state로 작업이 잘되는지?
UI element가 잘 업데이트 되는지?
각 각 목적에 맞는 테스트 코드를 할 수 있게 되었습니다.
4. Compose 사용
ViewModel에서 UI state를 엑티비티나 프레그먼트에서 사용할 때
일반 뷰에서 Flow나 LiveData를 사용하는 것보다 Compose를 사용한 가장 쉽게 UI state를 이용하여 UI업데이트를 할 수 있었습니다.
구글이 Compose 사용을 독려할 수 있는 또 하나의 목적이 생겼다고 볼 수 있는 것 같습니다.

 

그렇다면 다시 정리하여 클린 아키텍처 가이드라인으로 만든 예제 파일을 만드는 것을 한번 목표로 해보아야겠습니다.

그럼 여기까지 봐주셔서 감사합니다.

Comments