# 2026년 Android 모듈화: 멀티 모듈 아키텍처와 면접 질문 > Android 앱의 멀티 모듈 설계에 대해 상세히 알아봅니다. Gradle 버전 카탈로그, 컨벤션 플러그인, 모듈 간 통신 패턴, 그리고 기술 면접에서 자주 출제되는 질문과 답변을 소개합니다. - Published: 2026-09-17 - Updated: 2026-09-17 - Author: Anthony Fillion-Maillet - Tags: android, kotlin, architecture, gradle, modularization - Reading time: 12 min --- Android 모듈화는 단일 코드베이스를 유지보수성과 확장성이 뛰어난 시스템으로 전환하는 기법입니다. 적절하게 모듈화된 Android 앱은 빌드 시간 단축, 독립적인 테스트 실행, 그리고 신규 개발자 온보딩 기간의 대폭적인 단축을 달성합니다. > **모듈화의 기본 개념** > > 모듈화란 Android 앱을 명확한 경계를 가진 독립적인 Gradle 모듈로 분리하는 것입니다. 모듈 간의 낮은 결합도와 각 모듈 내의 높은 응집도가 기본 원칙입니다. Google의 "Now in Android" 레퍼런스 앱은 40개 이상의 모듈로 이 패턴을 보여줍니다. ## 멀티 모듈 아키텍처가 Android 앱에 중요한 이유 단일 모듈 Android 앱에서는 한 줄의 변경으로도 모든 것이 재컴파일됩니다. 네트워크 레이어의 수정이 UI 레이어의 재컴파일을 유발하고, 서로 다른 기능을 편집하는 두 명의 개발자가 공유 파일에서 머지 충돌을 일으킵니다. 증분 빌드에도 4분 이상 소요되는 경우가 흔합니다. 멀티 모듈 아키텍처는 이러한 문제점을 직접 해결합니다: - **빠른 빌드**: Gradle은 영향을 받은 모듈만 재컴파일합니다. `:feature:checkout`의 변경은 `:feature:profile`에 영향을 주지 않습니다. - **병렬 개발**: 팀은 정의된 API를 가진 개별 모듈을 소유합니다. 머지 충돌이 크게 감소합니다. - **독립적인 테스트**: 단위 테스트는 전체 앱을 로드하지 않고 단일 모듈에 대해 실행할 수 있습니다. - **코드 재사용**: `:core:ui` 디자인 시스템 모듈은 여러 앱에서 사용 가능한 라이브러리가 됩니다. [공식 Android 모듈화 가이드](https://developer.android.com/topic/modularization)에서는 단일 책임 원칙을 따라 함께 변경되는 기능이나 레이어를 중심으로 모듈을 구성할 것을 권장합니다. ## 모듈 유형과 책임 [Now in Android 리포지토리](https://github.com/android/nowinandroid)는 엔터프라이즈 앱에도 확장 가능한 검증된 모듈 분류를 수립했습니다. ```kotlin // settings.gradle.kts - 모듈 구조 include(":app") // Core 모듈 - 공유 유틸리티 include(":core:common") include(":core:data") include(":core:database") include(":core:datastore") include(":core:designsystem") include(":core:domain") include(":core:model") include(":core:network") include(":core:ui") // Feature 모듈 - 화면별 로직 include(":feature:home") include(":feature:search") include(":feature:bookmarks") include(":feature:settings") ``` **App 모듈**: `MainActivity`, 네비게이션 설정, 앱 레벨 스캐폴딩을 포함합니다. 모든 Feature 모듈과 필요한 Core 모듈에 의존합니다. **Core 모듈**: 기능 간에 공유되는 기능을 제공합니다. `:core:network`는 API 호출을 처리하고, `:core:database`는 Room 엔티티를 관리하며, `:core:designsystem`은 앱의 시각적 컴포넌트를 정의합니다. **Feature 모듈**: 단일 화면 또는 사용자 플로우를 캡슐화합니다. 각 Feature 모듈은 필요한 Core 모듈에만 의존하며, 다른 Feature 모듈에는 절대 의존하지 않습니다. 이 구조는 단방향 의존성을 강제합니다: Feature는 Core에 의존하고, App은 Feature에 의존합니다. ## 의존성 관리를 위한 Gradle 버전 카탈로그 25개 이상의 모듈 간에 버전 충돌 없이 의존성을 관리하려면 중앙화된 설정이 필요합니다. [Gradle 버전 카탈로그](https://docs.gradle.org/current/userguide/version_catalogs.html)는 단일 `libs.versions.toml` 파일로 이를 해결합니다. ```toml # gradle/libs.versions.toml [versions] agp = "8.7.0" kotlin = "2.1.0" compose-bom = "2026.09.00" ksp = "2.1.0-1.0.29" hilt = "2.54" room = "2.7.0" retrofit = "2.11.0" [libraries] androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version = "1.15.0" } androidx-compose-bom = { group = "androidx.compose", name = "compose-bom", version.ref = "compose-bom" } androidx-compose-ui = { group = "androidx.compose.ui", name = "ui" } androidx-compose-material3 = { group = "androidx.compose.material3", name = "material3" } hilt-android = { group = "com.google.dagger", name = "hilt-android", version.ref = "hilt" } hilt-compiler = { group = "com.google.dagger", name = "hilt-android-compiler", version.ref = "hilt" } room-runtime = { group = "androidx.room", name = "room-runtime", version.ref = "room" } room-ktx = { group = "androidx.room", name = "room-ktx", version.ref = "room" } room-compiler = { group = "androidx.room", name = "room-compiler", version.ref = "room" } retrofit-core = { group = "com.squareup.retrofit2", name = "retrofit", version.ref = "retrofit" } [bundles] compose = ["androidx-compose-ui", "androidx-compose-material3"] room = ["room-runtime", "room-ktx"] [plugins] android-application = { id = "com.android.application", version.ref = "agp" } android-library = { id = "com.android.library", version.ref = "agp" } kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" } ksp = { id = "com.google.devtools.ksp", version.ref = "ksp" } hilt = { id = "com.google.dagger.hilt.android", version.ref = "hilt" } ``` 각 모듈은 타입 안전한 접근자로 의존성을 참조합니다: ```kotlin // feature/home/build.gradle.kts dependencies { implementation(libs.bundles.compose) implementation(libs.hilt.android) ksp(libs.hilt.compiler) } ``` Gradle은 동기화 시 이러한 접근자를 생성하여 IDE 자동 완성과 컴파일 타임 검증을 가능하게 합니다. ## 컨벤션 플러그인: DRY한 빌드 설정 컨벤션 플러그인 없이 25개 모듈 프로젝트를 운영하면, 각 `build.gradle.kts`에서 동일한 `compileSdk`, `minSdk`, `composeOptions`, 플러그인 적용을 반복하게 됩니다. 서로 다른 모듈이 다른 설정을 사용할 위험도 있습니다. 컨벤션 플러그인은 이 로직을 `build-logic` 포함 빌드에 중앙화합니다. ```kotlin // build-logic/convention/src/main/kotlin/AndroidLibraryConventionPlugin.kt import com.android.build.api.dsl.LibraryExtension import org.gradle.api.Plugin import org.gradle.api.Project import org.gradle.kotlin.dsl.configure class AndroidLibraryConventionPlugin : Plugin { override fun apply(target: Project) { with(target) { pluginManager.apply("com.android.library") pluginManager.apply("org.jetbrains.kotlin.android") extensions.configure { compileSdk = 35 defaultConfig { minSdk = 26 testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" } compileOptions { sourceCompatibility = JavaVersion.VERSION_17 targetCompatibility = JavaVersion.VERSION_17 } } } } } ``` `build-logic/convention/build.gradle.kts`에서 플러그인을 등록합니다: ```kotlin // build-logic/convention/build.gradle.kts plugins { `kotlin-dsl` } dependencies { compileOnly(libs.android.gradlePlugin) compileOnly(libs.kotlin.gradlePlugin) } gradlePlugin { plugins { register("androidLibrary") { id = "myapp.android.library" implementationClass = "AndroidLibraryConventionPlugin" } register("androidFeature") { id = "myapp.android.feature" implementationClass = "AndroidFeatureConventionPlugin" } register("androidHilt") { id = "myapp.android.hilt" implementationClass = "AndroidHiltConventionPlugin" } } } ``` Feature 모듈은 이제 최소한의 설정만 필요합니다: ```kotlin // feature/home/build.gradle.kts plugins { alias(libs.plugins.myapp.android.feature) alias(libs.plugins.myapp.android.hilt) } dependencies { implementation(projects.core.data) implementation(projects.core.designsystem) } ``` Google의 [Now in Android build-logic](https://github.com/android/nowinandroid/blob/main/build-logic/README.md)도 정확히 이 패턴을 사용합니다. ## 모듈 간 통신 패턴 Feature 모듈은 서로 직접 의존해서는 안 됩니다. 이 제약은 병렬 컴파일을 유지하고 순환 의존성을 방지합니다. 세 가지 패턴이 크로스 피처 통신을 처리합니다. ### 공유 라우트를 통한 네비게이션 ```kotlin // core/navigation/src/main/kotlin/Routes.kt object Routes { const val HOME = "home" const val PRODUCT_DETAIL = "product/{productId}" const val CHECKOUT = "checkout" fun productDetail(productId: String) = "product/$productId" } ``` Feature 모듈은 어떤 모듈이 각 목적지를 구현하는지 알지 못한 채 `:core:navigation`에서 라우트를 가져옵니다. ### 공유 도메인 모델 ```kotlin // core/model/src/main/kotlin/Product.kt data class Product( val id: String, val name: String, val price: BigDecimal, val imageUrl: String ) ``` `:feature:catalog`와 `:feature:checkout` 모두 `Product` 클래스를 위해 `:core:model`에 의존합니다. ### 이벤트 버스 또는 공유 ViewModel 런타임 통신의 경우, `:core:common` 내의 공유 이벤트 버스나 스코프된 ViewModel이 크로스 피처 이벤트를 처리합니다: ```kotlin // core/common/src/main/kotlin/CartEventBus.kt object CartEventBus { private val _events = MutableSharedFlow() val events: SharedFlow = _events.asSharedFlow() suspend fun emit(event: CartEvent) { _events.emit(event) } } sealed interface CartEvent { data class ItemAdded(val productId: String) : CartEvent data class ItemRemoved(val productId: String) : CartEvent } ``` ## Android 모듈화 면접 질문 모듈화는 시니어 Android 면접에서 자주 출제됩니다. 면접관은 빌드 시스템에 대한 이해, 아키텍처 의사 결정, 실질적인 트레이드오프를 평가합니다. > **면접 팁** > > 면접에서 모듈화에 대해 논의할 때는 구체적인 지표를 참조하세요: 빌드 시간 개선, 팀 생산성 향상, 또는 프로젝트의 구체적인 모듈 수 등입니다. "관심사의 분리가 더 좋아진다"는 추상적인 답변보다 "모듈화 후 빌드 시간이 4분에서 45초로 단축되었다"는 답변이 더 설득력 있습니다. **Q: Gradle의 api 의존성과 implementation 의존성의 차이점은 무엇입니까?** `implementation` 의존성은 모듈 내부에 있습니다. 모듈의 사용자는 접근할 수 없습니다. `api` 의존성은 전이적으로 노출됩니다: 모듈 A가 `api(libs.retrofit)`를 사용하면, A에 의존하는 모듈 B는 Retrofit 클래스를 직접 사용할 수 있습니다. 경험 법칙: 기본적으로 `implementation`을 사용합니다. 의존성이 모듈의 공개 API의 일부인 경우에만 `api`를 사용합니다. `api`를 과다 사용하면 캡슐화가 깨지고 빌드가 느려집니다. **Q: 서로 의존하지 않는 Feature 모듈 간의 네비게이션을 어떻게 처리합니까?** 라우트 정의와 네비게이션 인터페이스를 포함하는 `:core:navigation` 모듈을 생성합니다. Feature 모듈은 이 공유 모듈에 의존합니다. App 모듈이 목적지를 구현에 연결합니다. 이는 의존성 역전 원칙을 따릅니다: 피처는 구체적인 구현이 아닌 추상화에 의존합니다. **Q: 모듈이 너무 많을 때의 트레이드오프는 무엇입니까?** 각 모듈은 Gradle 설정 오버헤드를 추가합니다: 빌드 파일, 리소스 처리, 매니페스트 병합. 초기 동기화 시간이 증가합니다. 전환점은 프로젝트마다 다르지만, 대규모 앱에서는 50개 이상의 모듈이 일반적입니다. 5명 이상의 개발자 팀에서는 장점(병렬 빌드, 독립적인 테스트, 명확한 소유권)이 일반적으로 오버헤드를 상회합니다. **Q: 컨벤션 플러그인은 buildSrc와 어떻게 다릅니까?** `buildSrc` 변경은 전체 프로젝트 재빌드를 트리거합니다. 포함 빌드 내의 컨벤션 플러그인은 사용하는 모듈이 변경될 때가 아니라 플러그인 코드가 변경될 때만 재컴파일됩니다. 이로 인해 컨벤션 플러그인은 반복 개발에서 더 빠릅니다. 또한 컨벤션 플러그인은 리포지토리 간 재사용을 위해 별도의 아티팩트로 게시할 수 있습니다. **Q: Feature 모듈을 단독으로 테스트하려면 어떻게 합니까?** Feature 모듈은 인터페이스를 통해 Core 모듈에 의존합니다. 테스트 픽스처에서 페이크 구현을 생성합니다: ```kotlin // core/data/src/testFixtures/kotlin/FakeProductRepository.kt class FakeProductRepository : ProductRepository { private val products = mutableListOf() override suspend fun getProducts(): List = products fun addProduct(product: Product) { products.add(product) } } ``` Feature 모듈 테스트는 Hilt의 테스트 API 또는 수동 생성자 주입을 통해 페이크를 주입합니다. Android 아키텍처에 관한 더 많은 연습 문제는 [MVVM 아키텍처 면접 문제](/technologies/android/interview-questions/android-mvvm-architecture)와 [의존성 주입 모듈](/technologies/android/interview-questions/android-dependency-injection)을 참조하세요. ## 멀티 모듈 Android 프로젝트의 핵심 포인트 - 기능(`:feature:home`, `:feature:checkout`)과 레이어(`:core:data`, `:core:ui`)로 모듈을 구성합니다. 피처는 Core에 의존하고, 서로에게는 의존하지 않습니다. - Gradle 버전 카탈로그(`libs.versions.toml`)를 사용하여 의존성 버전을 중앙화합니다. 번들은 관련 의존성을 그룹화하여 더 깔끔한 빌드 파일을 만듭니다. - `build-logic`에 컨벤션 플러그인을 구현하여 모듈 간 중복된 설정을 제거합니다. 하나의 플러그인 변경으로 모든 사용 모듈이 업데이트됩니다. - 단방향 의존성을 강제합니다: App은 Feature에 의존하고, Feature는 Core에 의존합니다. [Dependency Guard](https://github.com/dropbox/dependency-guard)와 같은 도구로 강제를 자동화할 수 있습니다. - 모듈화 전후의 빌드 성능을 측정합니다. 클린 빌드만이 아닌 증분 빌드 시간을 추적하여 개선을 검증합니다. - 모듈 경계를 안정적으로 유지합니다. 잦은 재구성은 모듈화의 이점을 무효화합니다. --- Source: SharpSkill (https://sharpskill.dev), tech interview preparation for your real stack. HTML version of this page: https://sharpskill.dev/ko/blog/android/android-modularization-multi-module-architecture-2026