Android

구글 권장 앱 아키텍처

chattymin 2024. 4. 19. 15:56
728x90
반응형

Clean : UI → Domain ← Data

Google : UI → Domain(optional) → Data

 

UI Layer

UI 레이어 : 화면에 애플리케이션 데이터를 표시

사용자 상호작용 또는 외부 입력으로 인해 데이터가 변할 때마다 변경사항을 반영하도록 UI가 업데이트 돼야함.

UI Element + UI State = UI

State를 활용해서 UI를 나타내줘야함

 

 

우수 사례

data class NewsUiState(
    val isSignedIn: Boolean = false,
    val isPremium: Boolean = false,
    val newsItems: List<NewsItemUiState> = listOf(),
    val userMessages: List<Message> = listOf()
)

data class NewsItemUiState(
    val title: String,
    val body: String,
    val bookmarked: Boolean = false,
    ...
)

 

UI elements

  • 화면에 데이터를 렌더링하는 UI 요소. 이러한 요소는 뷰 또는 Jetpack Compose 함수를 사용

State Holders

  • 데이터를 보유하고 이를 UI에 노출하며 로직을 처리하는 상태 홀더 → ViewModel

 

사용자 이벤트 처리

요약

  • ui에서 이벤트 발생
  • ui에 변화 → 직접적으로 변화시킴
  • 비즈니스 로직 필요 → 뷰모델에게 일 시킴
  • viewModel에서 이벤트 발생
  • uI를 변화시킴

 

 

Data Layer

데이터 레이어는 0개부터 여러 개의 저장소로 구성

앱에서 처리하는 다양한 유형의 데이터별로 저장소 클래스(Repository)를 만들어야함

데이터 레이어에는 비즈니스 로직이 포함됨.

  • 비즈니스 로직이란?
    앱에 가치를 부여하는 요소로, 앱의 데이터 생성, 저장, 변경 방식을 결정하는 규칙으로 구성됩니다.

Repository → DataSource 요청 과정

 

Repository 명명 규칙

데이터 유형 + 저장소

ex)

  • NewsRepository
  • MoviesRepository
  • PaymentsRepository
  • Repository 예시
class ExampleRepository(
    private val exampleRemoteDataSource: ExampleRemoteDataSource, // network
    private val exampleLocalDataSource: ExampleLocalDataSource // database
) {

    val data: Flow<Example> = ...

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

 

DataSource 명명 규칙

데이터 유형 + 소스 유형 + DataSource

ex)

  • NewsRemoteDataSource
  • NewsNetworkDataSource

 

 

Domain Layer

UI Layer와 Data Layer 사이에 있는 선택적 레이어.

복잡한 비즈니스 로직이나 여러 ViewModel에서 재사용되는 간단한 비즈니스 로직의 캡슐화를 담당. 그래서 복잡성을 처리하거나 재사용성을 선호하는 등 필요한 경우에만 사용

필요성

  • 코드 중복을 방지합니다.
  • 도메인 레이어 클래스를 사용하는 클래스의 가독성을 개선합니다.
  • 앱의 테스트 가능성을 높입니다.
  • 책임을 분할하여 대형 클래스를 피할 수 있습니다.

UseCase 명명 규칙

현재 시제의 동사 + 명사/대상(선택사항) + UseCase.

ex)

  • FormatDateUseCase
  • LogOutUserUseCase
  • GetLatestNewsWithAuthorsUseCase
  • MakeLoginRequestUseCase

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
) {
    suspend operator fun invoke(): List<ArticleWithAuthor> =
        withContext(defaultDispatcher) {
            val news = newsRepository.fetchLatestNews()
            val result: MutableList<ArticleWithAuthor> = mutableListOf()
            // This is not parallelized, the use case is linearly slow.
            for (article in news) {
                // The repository exposes suspend functions
                val author = authorsRepository.getAuthor(article.authorId)
                result.add(ArticleWithAuthor(article, author))
            }
            result
        }
}

 

 

종속성 관리

 

서비스 로케이터 패턴

Compositon has a 관계

목표 : 모듈화 수준 향상 → 클라이언트와 인터페이스 사이의 의존성 제거

인터페이스를 사용하면 필수적으로 구현체를 사용해야함.

이걸 클라이언트에서 구현체를 만들어 사용한다면 결국 인터페이스와 클라이언트 사이의 종속성은 사라지지 않음.

이를 막기 위해서 서비스 로케이터를 사용해 구현체를 가져옴.

인터페이스를 서비스 로케이터에 등록하고, 이를 클라이언트에서 가져다가 씀.

이렇게 되면 클라이언트는 인터페이스의 구현체를 모르지만, 가져다가 쓸 수 있음.

단점

  1. 모든 컴포넌트는 싱글톤 서비스 로케이터에 대한 레퍼런스를 가지고 있어야함.
  2. 서비스 로케이터 패턴은 테스트가 어려움.
  3. 인터페이스를 쉽게 바꿀 수 있어 문제가 생길 수 있는 인터페이스를 변경해버릴 수 있음.

⇒ DI라는 대안이 나옴.

DI(Dependency Injection)

Association has a 관계

파라미터 등으로 의존성을 주입받는 방식. 이렇게 하면 클라이언트는 구현체에 대해서 알 필요가 없다.

대표적으로 Hilt 라이브러리를 사용한다

→ 서비스 로케이터의 단점 해결

Domain Layer를 만들게 되면 Data Layer로의 직접적인 호출은 막는 규칙을 정해도 됨

단, 이렇게 되면 단순한 호출도 도메인 레이어를 거쳐야 한다는 단점이 있음

그래서 나는 우회해도 된다고 봄. 단, 연산이 필요하거나 추가적인 작업이 필요한 경우에는 UseCase를 지나가도록 해야한다고 생각함.

 

출처 : 공식문서

728x90
반응형