💡 / 키를 눌러 빠르게 검색하세요!

Android Multi module(멀티 모듈)의 dependency는 어떻게 하는 게 좋을까?

본문

아키텍쳐가 잘 적용되어 있다면 모듈 나누는 고민도 한 번쯤 해보았을 겁니다. 모듈을 나누는 건 어렵지 않습니다.

모듈을 어떤 식으로 나눌지가 어려운 고민이지만, 단순히 클래스의 기능별로 모듈을 나눌 수 있습니다.

  • Base 모듈 : BaseActivity, BaseViewModel, BaseFragment를 포함하는 모듈
  • Network 모듈 : 네트워크 관련 모듈
  • 유틸 모듈 : 공통 유틸

어떠한 기준으로 모듈을 분리할지만 잘 정하면 어렵지 않습니다.

하지만 사전 설계를 잘 해둬야 추후 중복 디펜던시 발생 및 이유 없는 모듈이 추가됨을 관리할 수 있으리라 생각됩니다.

이 글에서 알아볼 내용

  • 모듈을 나누는 모든 과정을 소개하지는 않습니다.
  • 모듈을 나누면서 디펜던시 의존성을 어떻게 가져갈지 간략하게 정리합니다.


모듈의 설계

모듈 설계 시 좋은 방법을 바로 소개하면

  • 정의 모듈 : interface 정의만을 가지는 모듈 하나
  • 정의에 대한 구현 모듈 : interface 정의에 대한 구현체를 가진 모듈 하나

이 두 개의 모듈은 아래와 같이 구분해 사용하는 게 가장 좋다고 생각합니다.

  • interface 정의만을 한 모듈은 실제 사용할 곳에서 dependencies 정의하고 사용합니다.
  • interface 정의에 대한 구현체를 가진 모듈은 코드 초기화에서 만 사용할 수 있도록 정의하는 게 좋습니다.

위와 같은 형태로 모듈을 만들었을 때 얻는 장점은 어디에서 어떤 모듈을 사용했는지 추적이 가능해지는 장점이 생깁니다.

당연히 모듈을 추가하는 디펜던시 방식에 따라 다를 수 있으니, 디펜던시 정의 방법을 먼저 살펴보고, 예제를 살펴보겠습니다.


디펜던시는 어떤 걸 사용할까?

모듈 간 디펜던시는 크게 implementation으로 정의하는 방법과 api로 정의하는 방법이 있습니다.

  • implementation : 대부분의 모듈에서 이를 활용하고, 의존 라이브러리 수정 시 implementation 하고 있는 모듈까지만 재빌드가 필요
  • api : 의존 라이브러리 수정 시 api를 하고 있는 하위 모듈까지 모두 재빌드 필요

재빌드 요구사항을 보면

  • implementation : 대상 모듈 A - 모듈 A를 implementation 하고 있는 모듈 B, 모듈 B를 implementation 하고 있는 모듈 C
    • 대상 모듈 A가 수정되면, 모듈 B는 재빌드 과정을 거칩니다.
  • api : 대상 모듈 A - 모듈 A를 api 하고 있는 모듈 B, 모듈 B를 implementation 하고 있는 모듈 C
    • 대상 모듈 A가 수정되면, 모듈 B, 모듈 C 모두 재빌드 과정을 거치게 됩니다.

디펜던시 내용은 본 블로그에 잘 나와 링크합니다. (Gradle dependency) api와 implementation 차이

모듈 간 디펜던시를 모두 implementation으로 걸 수도 있겠으나, 하위 모듈에서 어떤 모듈을 사용하고 있는지 모르게 하고 싶다면 api를 사용하는 것도 방법입니다.


모듈 설계 예제

  • library : viewController에 interface를 정의합니다.
  • library-impl : library 모듈에 대한 구현체입니다.
  • base : BaseFragment, BaseViewModel을 구성합니다.
    • 이 모듈에서는 librarylibrary-impl를 디펜던시로 사용합니다.
  • second-view : baselibrary 모듈을 사용해 화면을 구성합니다.
  • app : 앱 모듈에서 위 모듈을 조합해 실제 앱을 완성합니다.
    • 이 모듈에서는 위 모든 디펜던시 의존성을 가집니다.

모듈에 대한 샘플 코드


library 모듈

샘플에서 사용하는 controller에 대한 정의한 모듈입니다.

이 모듈은 아래와 같은 라이브러리에서 사용하는데, 실제 사용하는 모든 라이브러리에서 이를 활용해야 합니다.

  • library-impl 모듈 : 구현을 위한 모듈에서 활용합니다. implementation으로 사용합니다.
  • library-test 모듈 : test를 구현하기 위한 모듈에서 활용합니다. implementation으로 사용합니다.
  • base 모듈 : 구현체를 초기화하고, 이를 활용하기 위한 모듈입니다. 여기서는 implementation으로 사용합니다.
  • second-view 모듈 : 화면을 구성할 때 interface를 사용하기 위해 사용합니다.
  • app 모듈 : 앱에서 실행을 위함

image_01

위와 같이 정리할 수 있습니다. interface를 사용하기 때문에 어디에서든 implementation을 해서 사용하도록 설계하여 controller 사용 시 모든 모듈에서 명시적으로 추가해야 사용 가능합니다.


library-impl 모듈

library interface에 대한 구현체를 정의합니다.

이 모듈은 실제 구현체를 만드는 곳에서 활용합니다.

  • base 모듈 : 실제 구현체를 생성하고, 연결하기 위한 목적으로 사용합니다.
  • app 모듈 : 종합하기 위한 적용

image_02


library-test 모듈

test가 필요한 모듈에서 만 이를 추가하도록 만들었습니다.

여기서는 보통 library를 활용하는 모듈에서 함께 사용합니다.

  • second-view 모듈 : controller 테스트를 위해 사용합니다.
  • app 모듈 : 종합하기 위한 적용

image_03


base 모듈

base 모듈은 BaseFragment, BaseViewModel을 정의합니다.

이 모듈은 화면을 구성하는데 필수 요소로 화면을 그릴 때 사용합니다.

  • second-view 모듈 : 화면을 구성하기 위함
  • app 모듈 : 종합하기 위한 적용

image_04


second-view 모듈

여기서는 Second view를 정의하고, 이를 app 모듈에서 연결시킵니다.

  • app 모듈 : 화면을 연결합니다.

image_05


gradle.kts에서 forEach 이용한 모듈 Import

forEach 문을 활용해 모듈을 import 하면 편리한데 아래와 같이 다양한 implementation을

implementation(Dependency.Kotlin.stdLib)
implementation(Dependency.Coroutines.core)
implementation(Dependency.AndroidX.lifecycleCommonJava8)
implementation(Dependency.AndroidX.liveDataKtx)
implementation(Dependency.AndroidX.coreKtx)
implementation(Dependency.AndroidX.appCompat)
implementation(Dependency.AndroidX.constraintLayout)
implementation(Dependency.AndroidX.navigationFragmentKtx)

forEach를 이용해 아래와 같이 변경할 수 있습니다.

implementations(
    Dependency.Kotlin.stdLib,
    Dependency.Coroutines.core,
    Dependency.AndroidX.lifecycleCommonJava8,
    Dependency.AndroidX.liveDataKtx,
    Dependency.AndroidX.coreKtx,
    Dependency.AndroidX.appCompat,
    Dependency.AndroidX.constraintLayout,
    Dependency.AndroidX.navigationFragmentKtx,
)

implementations 함수를 미리 아래와 같이 만들어두면 위와 같이 활용할 수 있습니다.

fun DependencyHandlerScope.implementations(vararg argument: String) {
    argument.forEach {
        "implementation"(it)
    }
}


마무리

이와 같은 모듈의 형태는 하나의 예에 해당합니다. 저의 경우 위와 같은 형태로 모듈을 나누어 사용하고 있습니다.

디펜던시의 명확한 사용처를 확인할 수 있는 형태로 만들어 사용하고 있는데, 추후 어떻게 변할지는 아직은 잘 모르겠습니다.

디펜던시의 강제화를 통해 얻는 이익은 추적이 가능하다는 점을 미리 생각해 위와 같이 정의하였습니다.



About Taehwan

안드로이드 개발 12년차, 모바일앱 개발자 권태환입니다. 코드와 아키텍처, 그리고 개발 문화에 관심이 많습니다. GDG Korea Android와 DroidKnights에서 개발자 커뮤니티를 함께 만들어가고 있습니다.

Comments

테오 AI powered by Gemini 2.5 Flash
🤖
안녕하세요! 테오입니다. 😊
이 블로그의 모든 포스트를 학습했어요.
Android, Kotlin, 개발 관련 궁금한 점을 물어보세요!