Developing Myself Everyday
article thumbnail

 

사진: UnsplashKai Damm-Jonas

 

 

안드로이드 개발을 하면서 특정 라이브러리를 사용하거나, 다른 모듈에 필요한 것들이 있다면 그것들에 대해 의존성을 가지게 되고 필요한 곳에서 의존성 항목으로 추가하여야 합니다.

 

저는 개발을 하면서 필요한 의존성을 아무 생각 없이 주입해 왔는데, 이를 주입해 주는 메서드가 흔히 사용되는 `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 Studio  |  Android Developers

Android 스튜디오에서 Gradle 빌드 시스템을 이용하여 빌드 종속성을 추가하는 방법에 관해 알아보세요.

developer.android.com

 

Reduce overall build complexity by SimonMarquis · Pull Request #1088 · android/nowinandroid

Reduce the visibility of multiple Hilt Modules and implementations of public interfaces Correctly configure the visibility of dependencies: api when it's part of it's public api implementa...

github.com

profile

Developing Myself Everyday

@배준형

포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!