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을 구성합니다.- 이 모듈에서는
library
와library-impl
를 디펜던시로 사용합니다.
- 이 모듈에서는
second-view
:base
와library
모듈을 사용해 화면을 구성합니다.app
: 앱 모듈에서 위 모듈을 조합해 실제 앱을 완성합니다.- 이 모듈에서는 위 모든 디펜던시 의존성을 가집니다.
library 모듈
샘플에서 사용하는 controller에 대한 정의한 모듈입니다.
이 모듈은 아래와 같은 라이브러리에서 사용하는데, 실제 사용하는 모든 라이브러리에서 이를 활용해야 합니다.
library-impl 모듈
: 구현을 위한 모듈에서 활용합니다.implementation
으로 사용합니다.library-test 모듈
: test를 구현하기 위한 모듈에서 활용합니다.implementation
으로 사용합니다.base 모듈
: 구현체를 초기화하고, 이를 활용하기 위한 모듈입니다. 여기서는implementation
으로 사용합니다.second-view 모듈
: 화면을 구성할 때 interface를 사용하기 위해 사용합니다.app 모듈
: 앱에서 실행을 위함
위와 같이 정리할 수 있습니다. interface를 사용하기 때문에 어디에서든 implementation
을 해서 사용하도록 설계하여 controller 사용 시 모든 모듈에서 명시적으로 추가해야 사용 가능합니다.
library-impl 모듈
library interface에 대한 구현체를 정의합니다.
이 모듈은 실제 구현체를 만드는 곳에서 활용합니다.
base 모듈
: 실제 구현체를 생성하고, 연결하기 위한 목적으로 사용합니다.app 모듈
: 종합하기 위한 적용
library-test 모듈
test가 필요한 모듈에서 만 이를 추가하도록 만들었습니다.
여기서는 보통 library
를 활용하는 모듈에서 함께 사용합니다.
second-view 모듈
: controller 테스트를 위해 사용합니다.app 모듈
: 종합하기 위한 적용
base 모듈
base 모듈은 BaseFragment, BaseViewModel을 정의합니다.
이 모듈은 화면을 구성하는데 필수 요소로 화면을 그릴 때 사용합니다.
second-view 모듈
: 화면을 구성하기 위함app 모듈
: 종합하기 위한 적용
second-view 모듈
여기서는 Second view를 정의하고, 이를 app 모듈에서 연결시킵니다.
app 모듈
: 화면을 연결합니다.
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)
}
}
마무리
이와 같은 모듈의 형태는 하나의 예에 해당합니다. 저의 경우 위와 같은 형태로 모듈을 나누어 사용하고 있습니다.
디펜던시의 명확한 사용처를 확인할 수 있는 형태로 만들어 사용하고 있는데, 추후 어떻게 변할지는 아직은 잘 모르겠습니다.
디펜던시의 강제화를 통해 얻는 이익은 추적이 가능하다는 점을 미리 생각해 위와 같이 정의하였습니다.
Comments