안드로이드 앱 클린 아키텍처 - 도메인 레이어

이윤설·2024년 9월 22일
0

안드로이드 연구소

목록 보기
3/33

도메인 레이어

도메인 레이어는 UI 레이어와 데이터 레이어 사이에 있는 선택적 레이어다.

도메인 레이어는 복잡한 비즈니스 로직이나 여러 ViewModel에서 재사용되는 간단한 비즈니스 로직의 캡슐화를 담당한다. 모든 앱에 이러한 요구사항이 있는 것은 아니므로 이 레이어는 선택사항이다. 따라서 복잡성을 처리하거나 재사용성을 선호하는 등 필요한 경우에만 도메인 레이어를 사용한다.

도메인 레이어는 다음과 같은 이점을 제공한다.

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

이러한 클래스를 간단하고 가볍게 유지하려면 각 사용 사례에서는 기능 하나만 담당해야 하고 변경 가능한 데이터를 포함해서는 안 된다. 대신 UI 레이어 또는 데이터 레이어에서 변경 가능한 데이터를 처리해야 한다.

이 가이드의 이름 지정 규칙

이 가이드에서는 사용 사례(UseCase)를 단일 작업 단위로 정의하며, 이름은 다음과 같은 규칙을 따른다:

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

예를 들어, FormatDateUseCase, LogOutUserUseCase, GetLatestNewsWithAuthorsUseCase, MakeLoginRequestUseCase와 같이 작성된다. 이 규칙을 따르면 클래스의 역할이 명확해지고 가독성이 좋아진다.


사용 사례(use case) 또는 Domain Model

사용 사례(Use Case) 클래스는 소프트웨어 개발, 특히 안드로이드 앱 아키텍처에서 중요한 개념이다. 사용 사례 클래스는 Clean Architecture나 MVVM+Clean Architecture와 같은 아키텍처 패턴에서 중요한 역할을 한다.

사용 사례(Use Case)의 정의:

사용 사례 클래스는 애플리케이션의 비즈니스 로직을 캡슐화하는 컴포넌트다. 이는 특정 기능이나 작업을 수행하기 위한 단일 책임을 가진 클래스로, 일반적으로 하나 이상의 저장소(Repository)를 사용하여 데이터를 처리하고 비즈니스 규칙을 적용한다.

주요 특징

  1. 단일 책임 원칙(Single Responsibility Principle) 준수
  2. 재사용 가능한 비즈니스 로직 캡슐화
  3. UI 레이어와 데이터 레이어 사이의 중재자 역할
  4. 테스트 용이성 향상
// 주문 처리 사용 사례
class ProcessOrderUseCase(
    private val orderRepository: OrderRepository,
    private val paymentRepository: PaymentRepository,
    private val inventoryRepository: InventoryRepository
) {
    suspend operator fun invoke(order: Order): Result<OrderConfirmation> {
        return try {
            // 재고 확인
            if (!inventoryRepository.checkAvailability(order.items)) {
                throw InsufficientInventoryException()
            }
            
            // 결제 처리
            val paymentResult = paymentRepository.processPayment(order.total)
            if (!paymentResult.isSuccessful) {
                throw PaymentFailedException()
            }
            
            // 주문 생성
            val confirmedOrder = orderRepository.createOrder(order)
            
            // 재고 업데이트
            inventoryRepository.updateInventory(order.items)
            
            Result.success(OrderConfirmation(confirmedOrder.id, confirmedOrder.estimatedDelivery))
        } catch (e: Exception) {
            Result.failure(e)
        }
    }
}

// ViewModel에서의 사용 예
class OrderViewModel(private val processOrderUseCase: ProcessOrderUseCase) : ViewModel() {
    fun placeOrder(order: Order) {
        viewModelScope.launch {
            val result = processOrderUseCase(order)
            // 결과 처리
        }
    }
}

ProcessOrderUseCase: 주문 처리의 전체 프로세스를 캡슐화한다.
재고 확인, 결제 처리, 주문 생성, 재고 업데이트 등 복잡한 비즈니스 로직을 포함한다.

이러한 사용 사례 클래스들은 다음과 같은 이점을 제공한다.

  1. 비즈니스 로직의 모듈화: 각 사용 사례는 특정 기능에 집중한다.
  2. 재사용성: 다른 부분의 코드에서 쉽게 재사용할 수 있다.
  3. 테스트 용이성: 비즈니스 로직을 독립적으로 테스트할 수 있다.
  4. 관심사의 분리: UI 로직과 비즈니스 로직을 명확히 분리한다.

종속 항목

일반적인 안드로이드 앱 아키텍처에서 사용 사례 클래스UI 레이어의 ViewModel데이터 레이어의 저장소 사이에 위치한다. 즉, 사용 사례는 주로 도메인 레이어에 종속되며, 저장소와 동일한 방식으로 코루틴(Kotlin)이나 콜백(Java)을 통해 UI 레이어와 통신한다.
예를 들어, 뉴스 앱의 사용 사례 클래스는 NewsRepositoryAuthorsRepository라는 두 개의 저장소 클래스를 결합하여 최신 뉴스와 관련 작성자 정보를 가져올 수 있다.

class GetLatestNewsWithAuthorsUseCase(
  private val newsRepository: NewsRepository,
  private val authorsRepository: AuthorsRepository
) {
    // 로직 구현
}

사용 사례 클래스는 재사용 가능한 로직을 포함하기 때문에 다른 사용 사례에서 이를 사용할 수 있다. 예를 들어 GetLatestNewsWithAuthorsUseCaseFormatDateUseCase 같은 다른 사용 사례를 호출할 수도 있다.


Kotlin에서 사용 사례 호출

Kotlin에서는 operator 수정자와 invoke() 함수를 사용하여 클래스 인스턴스를 함수처럼 호출할 수 있다. 예를 들어, 다음과 같은 FormatDateUseCase는 날짜를 형식화하는 기능을 캡슐화하고, 이를 마치 함수처럼 호출할 수 있다.

class FormatDateUseCase(userRepository: UserRepository) {
    private val formatter = SimpleDateFormat(
        userRepository.getPreferredDateFormat(),
        userRepository.getPreferredLocale()
    )

    operator fun invoke(date: Date): String {
        return formatter.format(date)
    }
}

이를 사용하는 ViewModel 클래스는 다음과 같이 invoke() 메서드를 통해 사용 사례를 호출할 수 있다.

class MyViewModel(formatDateUseCase: FormatDateUseCase) : ViewModel() {
    init {
        val today = Calendar.getInstance().time
        val formattedDate = formatDateUseCase(today)
        // 기타 로직
    }
}

이러한 방법은 Kotlin의 기능을 활용하여 코드의 가독성을 높이고, 간편하게 사용 사례를 호출할 수 있도록 한다.


수명 주기

사용 사례는 고유한 수명 주기를 갖지 않는다. 대신, 이를 호출하는 클래스(ViewModel, 서비스, Application 등)의 수명 주기를 따른다.
사용 사례는 변경 가능한 데이터를 포함해서는 안 되므로, 새로운 인스턴스를 필요할 때마다 생성하여 사용해야 한다.


스레딩

사용 사례는 기본 안전성을 보장해야 하며, 장시간 실행되는 작업은 별도의 스레드에서 처리되어야 한다. 예를 들어, 백그라운드에서 긴 작업을 실행할 때는 CoroutineDispatcher를 사용하여 이를 명시적으로 처리한다.

class MyUseCase(
    private val defaultDispatcher: CoroutineDispatcher = Dispatchers.Default
) {
    suspend operator fun invoke(): Result = withContext(defaultDispatcher) {
        // 장기 실행 작업 수행
    }
}

이와 같은 방식으로 스레드 처리를 통해 성능을 최적화할 수 있다.


일반적인 작업

재사용 가능한 간단한 비즈니스 로직

UI 레이어에서 반복적으로 사용되는 비즈니스 로직은 사용 사례 클래스로 분리하여 캡슐화할 필요가 있다. 이렇게 하면, 여러 곳에서 동일한 로직을 사용할 때 변경 사항을 한 곳에서만 수정하면 되어 유지보수가 훨씬 간편해진다.

예를 들어, 날짜 형식 처리에 관련된 비즈니스 요구사항이 변경된다면, 날짜를 처리하는 사용 사례를 한 번만 수정하면 이 로직을 사용하는 모든 화면에 변경 사항이 반영된다.
또한, 이렇게 분리된 로직은 개별적으로 테스트하기도 쉬워져, 품질 유지에도 큰 도움이 된다.


저장소 결합

뉴스 앱을 예로 들어보자. 이 앱에는 뉴스 데이터를 다루는 NewsRepository와 작성자 정보를 다루는 AuthorsRepository라는 두 가지 저장소 클래스가 있다.

NewsRepository는 기사(Article) 목록을 제공하지만, 각 기사는 작성자의 이름만 포함하고 있다.
만약 화면에 작성자의 더 자세한 정보를 보여주고 싶다면, 해당 정보는 AuthorsRepository에서 가져와야 한다.

이러한 두 저장소의 데이터를 결합하는 로직이 복잡해질 수 있기 때문에, GetLatestNewsWithAuthorsUseCase라는 사용 사례 클래스를 만들어서 이 로직을 분리하고, ViewModel에서 이 복잡한 로직을 깔끔하게 추상화할 수 있다.
이를 통해 코드 가독성을 높이고, 나중에 재사용하거나 테스트할 때도 편리하다.


기타 소비자

도메인 레이어의 사용 사례는 UI 레이어 외에도 서비스, Application 클래스 등에서 재사용될 수 있다. 이를 통해 여러 플랫폼(TV, Wear 등)에서 동일한 비즈니스 로직을 공유할 수 있다.


데이터 레이어 액세스 제한

UI 레이어가 데이터 레이어에 직접 접근하지 않고 도메인 레이어를 통해서만 데이터에 접근하도록 제한할 수 있다. 이는 앱의 구조를 엄격히 통제할 수 있게 해주며, 로깅이나 분석과 같은 추가 로직을 손쉽게 삽입할 수 있다.
그러나 간단한 함수 호출에도 복잡성을 더할 수 있으므로, 필요한 경우에만 이 방식을 사용하는 것이 좋다.


테스트

도메인 레이어는 가짜 저장소(Mock Repository)를 사용하여 독립적으로 테스트할 수 있다.
이를 통해 비즈니스 로직을 개별적으로 검증할 수 있으며, UI 테스트와 마찬가지로 명확한 테스트 시나리오를 작성할 수 있다.


https://developer.android.com/topic/architecture/domain-layer?hl=ko&_gl=1*mmit9e*_up*MQ..*_ga*NjU2MDc3Ni4xNzI3MDA0NDQ5*_ga_6HH9YJMN9M*MTcyNzAwNDQ0OC4xLjAuMTcyNzAwNDQ0OC4wLjAuMTE5OTAzMjM0Nw..#testing

profile
화려한 외면이 아닌 단단한 내면

0개의 댓글

관련 채용 정보