안드로이드 개발을 하면서 특정 라이브러리를 사용하거나, 다른 모듈에 필요한 것들이 있다면 그것들에 대해 의존성을 가지게 되고 필요한 곳에서 의존성 항목으로 추가하여야 합니다.
저는 개발을 하면서 필요한 의존성을 아무 생각 없이 주입해 왔는데, 이를 주입해 주는 메서드가 흔히 사용되는 `implementation` 뿐만이 아니라 다른 것들이 있다는 것을 알게 되었습니다.
그래서 이번 게시글에서는 각각의 메서드들에 대해 알아보고, 이들을 어떻게 사용하는 것이 좋은지 공부해보고자 합니다.
의존성 구성 방식 - implmentation vs api
안드로이드에서 의존성을 추가하기 위해서는 `dependencies` block 안에서 작성하여야 합니다. 이 블록 안에서 아래와 같은 의존성 구성 방식들을 사용하여 의존성을 선언할 수 있습니다.
implementation
implementation으로 의존성을 정의하면 Gradle은 이 의존성을 컴파일 클래스 경로에 추가하고 빌드 출력에 의존성 항목을 패키징 합니다. implementation으로 정의한 의존성은 컴파일할 때 다른 모듈에 종속되지 않도록 하여, 다른 모듈에서는 사용할 수 없습니다.
이해가 잘 안되신다면, 일단 다음 구성 방식을 보시겠습니다.
api
api로 의존성을 정의하면 implementation과 동일하게 의존성을 컴파일 클래스 경로에 추가하고 빌드 출력에 의존성 항목을 패키징 합니다. 다만, implementation과 다르게 api로 정의한 의존성은 그 의존성을 다른 모듈에 전달하여 런타임 및 컴파일 시간에 사용할 수 있도록 합니다.
이해하기 쉽게 예시를 들어보겠습니다. 만약에 A 모듈에서 레트로핏 통신을 하기 위해서 관련된 의존성을 아래와 같이 정의했다고 생각해 보겠습니다.
그리고 B 모듈에서 모듈 A에 대한 의존성이 필요하다고 가정해 보겠습니다. 먼저 각각의 의존성이 implementation으로 되어 있을 경우입니다.
implementation은 의존성이 컴파일될 때, 다른 모듈에 의존되지 않게 합니다. 즉, 모듈 A에서는 Retrofit2을 사용할 수 있지만, 모듈 B에서는 Retrofit2를 사용할 수 없다는 것을 의미합니다. 모듈 B에서는 오직 모듈 A에만 접근할 수 있습니다.
이젠 모듈 A에서 Retrofit2에 대한 의존성을 api로 추가하였을 경우입니다.
이 경우에는 Refrofit2에 대한 의존성이 모듈 B에게도 전달되어 모듈 B에서 Retrofit2를 사용할 수 있습니다.
빌드 시간의 차이
당연하게도 api가 implementation에 비해 전달해야 하는 의존성이 더 많아지므로 빌드 시간이 implementation에 비해 크게 증가할 수 있습니다. 예를 들어, implementation 의존성의 API가 변경되면 Gradle은 해당 의존성과 그 의존성에 직접 의존하는 모듈만 다시 컴파일합니다.
이러한 차이 때문에 기본적으로는 implementation을 통해 의존성을 정의하는 것이 좋고 api를 사용할 경우에는 매우 신중하게 접근하여야 합니다.
멀티 모듈에서의 api의 활용 방안
2가지의 방식은 싱글 모듈 프로젝트에서는 차이가 실질적으로 나타나지는 않았습니다. 다만 멀티 모듈에서는 잘 활용한다면, 의존성을 매우 효율적이고 깔끔하게 관리할 수 있습니다.
저의 프로젝트를 한번 api를 활용해서 개선해 보겠습니다. 저는 feature 모듈에 필요한 공통된 의존성을 build-logic에서 플러그인으로 정의하여 사용하고 있습니다.
feature 모듈 공통 의존성
모듈에 대한 의존성만을 보면 현재 모든 feature 모듈들은 `model`, `designsystem`, domain`, `ui`, `navigation` 모듈에 대해 의존성을 가지고 있습니다.
import com.jun.tripguide.configureHiltAndroid
import com.jun.tripguide.libs
plugins {
id("jun.android.library")
id("jun.android.compose")
}
android {
defaultConfig {
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
}
}
configureHiltAndroid()
dependencies {
implementation(project(":core:model"))
implementation(project(":core:designsystem"))
implementation(project(":core:domain"))
implementation(project(":core:ui"))
implementation(project(":core:navigation"))
val libs = project.extensions.libs
implementation(libs.findLibrary("hilt.navigation.compose").get())
implementation(libs.findLibrary("androidx.compose.navigation").get())
implementation(libs.findLibrary("androidx-compose-material-icon").get())
androidTestImplementation(libs.findLibrary("androidx.compose.navigation.test").get())
implementation(libs.findLibrary("androidx.lifecycle.viewModelCompose").get())
implementation(libs.findLibrary("androidx.lifecycle.runtimeCompose").get())
implementation(libs.findLibrary("inject").get())
}
이 중 ui 모듈을 자세히 보겠습니다.
ui 모듈 build.gradle.kts
ui 모듈에도 공통되게 `model`, `designsystem`에 대한 의존성이 정의되어 있습니다.
import com.jun.tripguide.setNamespace
plugins {
alias(libs.plugins.jun.android.library)
alias(libs.plugins.jun.android.compose)
}
android {
setNamespace("core.ui")
}
dependencies {
implementation(projects.core.model)
implementation(projects.core.designsystem)
}
여기서 model과 ui에 대한 의존성만을 그래프로 나타내 보면 아래와 같습니다. 각각의 feature 모듈은 model과 ui에 대해 의존성을 가지고, 이는 그래프에서 매우 복잡하게 나타내집니다.
이러한 구조에서 모든 feature 모듈들이 반드시 ui에 대해 의존성을 가져야 한다면, ui모듈에 `model`, `designsystem`에 대한 의존성이 정의되어 있으므로, api를 사용한다면 그래프를 개선할 수 있습니다.
import com.jun.tripguide.setNamespace
plugins {
alias(libs.plugins.jun.android.library)
alias(libs.plugins.jun.android.compose)
}
android {
setNamespace("core.ui")
}
dependencies {
api(projects.core.model) // api 사용
api(projects.core.designsystem) // api 사용
}
그래프를 아래와 같이 개선할 수 있습니다.
단점
이러한 의존성 정의 방식은 빌드 복잡성을 줄이고, 그래프를 개선할 수 있습니다. 하지만, api로 정의된 의존성을 확인하기 위해서는 그래프를 직접 타고 올라가면서 어디서 정의되었는지를 확인해야 합니다.
이는 api를 잘 이해하고 있고, 프로젝트의 의존성 그래프 구조를 이해하고 있는 사람이라면 이해하는데 많은 시간이 필요하지 않겠지만, 해당 프로젝트의 구조를 처음 접하거나 api에 대해 잘 모르는 사람이라면 이해하는데 많은 시간이 걸립니다.
그렇기 때문에 api를 통해 의존성을 정의할 때에는 매우 신중하게 접근해야 한다고 생각합니다.
Reference
'Android' 카테고리의 다른 글
Android Intent의 내부 동작 방식과 데이터 전달 (0) | 2024.12.11 |
---|---|
Xml, Compose 그리고 명령형과 선언형 (0) | 2024.03.08 |
그냥 지나쳤던 안드로이드의 크기 단위 (0) | 2024.02.02 |
안드로이드에서 사용자의 위치를 가져오는 방법 (1) | 2024.01.07 |
안드로이드에서 Json을 사용하는 다양한 방법 (0) | 2023.12.03 |