본문 바로가기

안드로이드/클린 아키텍처

[안드로이드 클린 아키텍처 시리즈] Domain Layer 구현 #5


1. 안드로이드 클린 아키텍처 Domain Layer

안녕하세요, 오늘은 클린 아키텍처의 핵심 구성 요소 중 하나인 Domain Layer에 대해 자세히 살펴보겠습니다. 클린 아키텍처 구현 시리즈에서 Domain Layer부터 다루는 이유는 어떤 계층에도 의존하고 있지 않기 때문입니다. 그래서 Domain Layer부터 구현하고 Data Layer, UI Layer 순서로 포스팅할 예정입니다. 그럼 지금부터 Domain Layer를 구현하겠습니다.

Domain Layer에 entity, repository interface, usecase를 구현한 모습

2. Domain Layer의 구성 요소

Domain Layer는 크게 엔터티(Entity), 레포지토리 인터페이스(Repository Interface), 유스케이스(UseCase)로 구성됩니다. 필요에 따라서 util 함수가 추가될 수 있습니다.

2-1. 엔티티(Entity)

Domain Layer에서 시작점은 엔티티를 정의하는 것입니다. 엔터티는 애플리케이션의 비즈니스 로직을 반영하는 핵심 데이터 모델입니다. 예를 들어, 사용자(User), 주문(Order), 상품(Product) 등이 엔터티가 될 수 있습니다. 엔터티는 비즈니스 로직을 담고 있으며, 이를 통해 애플리케이션이 어떤 작업을 수행할 수 있는지 정의됩니다.

data class MovieEntity(
    val id: Int,
    val title: String,
    val description: String,
    val category: String,
    val image: String,
    val backgroundUrl: String
)

2-2. 레포지토리 인터페이스 (Repository Interface)

레포지토리 인터페이스는 Domain Layer와 Data Layer 사이의 인터페이스를 정의합니다. 이 인터페이스를 통해, 데이터를 어떻게 조회하고 저장할지에 대한 세부 사항을 추상화합니다. 레포지토리 인터페이스를 정의함으로써, 데이터 소스의 구현 세부 사항으로부터 비즈니스 로직을 분리할 수 있습니다. (의존성 역전 법칙)
레포지토리 인터페이스 구현은 Data Layer에서 수행합니다.

interface MovieRepository {
    suspend fun getMovieDetail(movieId: Int): ApiResult<MovieEntity>
}

2-3. 유스케이스(UseCase)

유스케이스는 애플리케이션에서 사용자가 수행할 수 있는 비즈니스 프로세스를 나타냅니다. 각 유스케이스는 특정 작업을 완료하기 위한 작업을 정의하게 됩니다. 예를 들어, 사용자의 이메일을 업데이트하는 유스케이스는 다음과 같습니다.

class GetMovieDetail(
    private val movieRepository: MovieRepository
) {
    suspend operator fun invoke(movieId: Int): ApiResult<MovieEntity> = movieRepository.getMovieDetail(movieId)
}

2-4. ApiResult 유틸리티 함수

데이터 처리의 결과를 명확하게 표현하기 위해 ApiResult라는 Sealed Class를 구현했습니다. 이 함수는 데이터 처리 결과가 성공인지, 오류인지를 판단하게 됩니다.

sealed class ApiResult<T> {
    data class Success<T>(val data: T) : ApiResult<T>()
    data class Error<T>(val error: Throwable) : ApiResult<T>()
}

inline fun <T, R> ApiResult<T>.getResult(
    success: (Success<T>) -> R,
    error: (Error<T>) -> R
): R = when (this) {
    is Success -> success(this)
    is Error -> error(this)
}

inline fun <T> ApiResult<T>.onSuccess(
    action: (T) -> Unit
): ApiResult<T> {
    if (this is Success) action(data)
    return this
}

inline fun <T> ApiResult<T>.onError(
    action: (Throwable) -> Unit
): ApiResult<T> {
    if (this is Error) action(error)
    return this
}

 

3. Domain Layer 구현 정리

이번 포스팅에서는 클린 아키텍처의 핵심 구성 요소 중 하나인 Domain Layer에 대해 종합적으로 살펴보았습니다. Domain Layer는 애플리케이션의 비즈니스 로직과 핵심 규칙을 담당하는 중요한 계층으로, 엔티티(Entity), 유즈케이스(UseCase), 그리고 레포지토리 인터페이스(Repository Interface)로 구성됩니다.

Domain Layer가 클린 아키텍처 구현 시리즈에서 먼저 다뤄지는 이유는 다른 어떤 계층과도 의존하지 않기 때문이라고 말씀드렸었습니다. 이는 마치 건축에서 견고한 기초 위에 구조물을 세우는 것과 유사한 것 같습니다. Domain Layer에서 정의된 엔티티, 인터페이스, 유스케이스가 나침반 역할을 하며, 이를 기반으로 UI Layer와 Data Layer가 구현되기 때문입니다.

즉, Domain Layer를 먼저 구현하고 나머지 계층을 Domain Layer의 로직에 맞추어 구현함으로써, 애플리케이션 개발을 체계적이고 효율적으로 진행할 수 있는 토대를 마련한다고 생각합니다.

다음 시간에는 Data Layer에 대해 자세히 다뤄보겠습니다.