Data 모듈은 순수 Kotlin 모듈일 수 있을까?

유진·2025년 12월 3일

Android

목록 보기
12/18

Android 프로젝트를 멀티 모듈 구조로 구성할 때 가장 자주 사용하는 형태는 다음과 같다.

  • Presentation: Android UI 모듈
  • Domain: 순수 Kotlin 모듈
  • Data: Android UI 모듈일 수도 있고 순수 Kotlin 모듈일 수도 있다.

Domain 모듈이 순수 Kotlin 모듈인 것은 다들 아는 사실일 것이다.

그런데, Data 모듈은 둘 다 될 수도 있다고 한다.
여기에 추가로 알 수 있는 사항은 Android 의존성 없이 JVM 기반으로 유지하면 테스트 환경이 가벼워지고, 순수한 비즈니스 로직만 다루도록 설계할 수 있다는 점이다.

나 역시 초기에는 Ktor를 사용해 네트워크 통신을 구현했기 때문에 Data 모듈을 Kotlin JVM 모듈로 구성했다.
Ktor는 멀티플랫폼을 지원하고 Android 종속성이 없는 client-core, client-cio 엔진만 사용하면 JVM 환경에서도 문제 없이 동작한다.

그러나 Paging3를 도입하면서 상황이 달라졌다.


Paging3 도입 이전: Data 모듈은 순수 Kotlin JVM 모듈이어도 충분하다

Ktor 기반으로 Repository를 구성하던 시점에는 다음과 같은 구조가 가능했다.

  • API 통신: Ktor client-core, cio 엔진 사용
  • DTO 변환: Kotlin Serialization
  • Repository, Datasource: Kotlin JVM 환경에서 모두 가능
  • Domain으로 전달: Result, Model, Flow 등

이 경우 Data 모듈에는 Android 종속성이 전혀 없기 때문에 kotlin("jvm") 모듈로 구현해도 문제가 없다.


Paging3 도입 이후: Data 모듈은 Android 모듈이 될 수밖에 없다

하지만 무한 스크롤 기능을 위해 Paging3를 도입하면서 제약이 생긴다.

Paging3는 다음과 같이 세 개의 모듈로 나뉜다.

  • paging-common (Android 의존성 없음)
  • paging-runtime (Android 의존성 있음)
  • paging-compose (UI 전용)

여기서 핵심은 Pager 클래스가 paging-runtime에 포함되어 있다는 점이다.
Repository에서 Pager를 사용해 PagingData 스트림을 생성해야 하는데, 이 시점에서 Android 의존성이 필요해진다.

예시:

fun getCharacters(): Flow<PagingData<Character>> {
    return Pager(
        config = PagingConfig(pageSize = 20),
        pagingSourceFactory = { CharacterPagingSource(api) }
    ).flow
}

Pager는 내부적으로 AndroidX 라이브러리와 연동되어 있고, paging-runtime 또한 AAR(Android Archive) 형태로 배포된다.
따라서 다음과 같은 문제가 발생한다.

  • JVM 모듈은 AAR 의존성을 받을 수 없다.
  • pager-runtime 자체가 Android 모듈 전용이다.
  • Kotlin JVM 모듈에서 Pager를 import할 수 없다.

즉, Repository에서 Pager를 생성하는 순간 Data 모듈은 반드시 Android 모듈이어야 한다.


왜 Pager를 Presentation이 아닌 Data 계층에서 생성해야 하는가

일부 개발자는 Pager 생성을 Presentation 계층에서 수행하여 Data 모듈을 순수 Kotlin 모듈로 유지할 수 있다고 생각할 수 있다.
그러나 이는 클린 아키텍처 관점에서 좋지 않은 설계다.

Pager는 다음 역할을 수행한다.

  • 페이지네이션 로직 정의
  • PagingSource 생성
  • 네트워크 로드 관리

이 로직은 "UI 로직"이 아니라 "데이터 로딩 전략"에 속한다.
즉, Repository가 책임져야 하는 부분이다.
Presentation 계층으로 Pager 로직을 밀어 넣으면 UI와 데이터 로직이 결합되어 아키텍처가 무너진다.

따라서 다음이 가장 자연스러운 구조다.

  • Data: Pager 생성, PagingSource 제공
  • Domain: PagingData 그대로 전달
  • Presentation: collect, Paging Compose 사용

이 구조를 유지하려면 Data 모듈은 Android Library 모듈이어야 한다.


Data 모듈을 Android Library로 변경한 후의 장점

  1. Repository가 Paging3를 자연스럽게 책임질 수 있다.
  2. PagingSource, Pager, RemoteMediator 등을 모두 Data 계층에서 관리할 수 있다.
  3. Presentation은 paging-compose만 사용하고 로직은 완전히 분리된다.
  4. Domain은 paging-common에 포함된 PagingData, PagingSource를 문제 없이 사용할 수 있다.

즉, Paging3를 올바르게 사용하기 위해서는 Data 모듈이 Android 모듈로 변경되는 것이 불가피하며, 오히려 아키텍처 관점에서도 더 자연스러운 선택이다.


정리

  • Ktor만 사용할 때는 Data 모듈을 JVM 기반으로 설계할 수 있었다.
  • Paging3를 도입하면 Repository에서 Pager를 생성해야 한다.
  • Pager는 Android 종속성을 가진 paging-runtime에 포함되어 있다.
  • 따라서 Data 모듈은 Android Library 모듈로 전환되어야 한다.
  • 이는 클린 아키텍처의 책임 분리를 지키는 측면에서도 더 적합하다.

이 변화는 단순히 모듈 타입을 변경하는 것 이상의 의미가 있다.
Android 환경에서 멀티 모듈 아키텍처를 사용한다면, 기술 스택에 따라 모듈의 성격이 달라질 수 있고 개발자는 그 이유와 구조적 적합성을 이해해야 한다.

profile
안드로이드... 좋아하세요?

0개의 댓글