--- id: android-modularization title: Android Modularization — Feature / Core / App category: Coding status: draft source_trust_level: B verification_status: conceptual created_at: 2026-05-09 updated_at: 2026-05-09 tags: [android, gradle, modularization, vibe-coding] tech_stack: { language: "Kotlin / Gradle", applicable_to: ["Android"] } applied_in: [] aliases: [feature module, core module, build time, dynamic feature] --- # Android Modularization > 한 모듈 앱은 빌드 시간과 응집도가 폭발한다. **feature : core : data : app** 4계층 분리 + **단방향 의존성** = 빠른 빌드 + 명확한 책임. 잘못된 의존성은 cyclical → gradle 폭사. ## 📖 핵심 개념 일반적 4계층: - **app**: Application class, AppNav. 모든 feature import. - **feature:xxx**: 한 feature 의 UI + ViewModel. - **core:**: 공통 (ui-kit, designsystem, network, database). - **data:xxx**: repository, remote, local. 규칙: 모든 의존성 위에서 아래로. feature 끼리는 직접 X (app 만 안다). ## 💻 코드 패턴 ### Project 구조 ``` :app :feature:home :feature:profile :feature:order :core:ui (Compose theme, components) :core:network (Retrofit, OkHttp) :core:database (Room) :core:common :data:user (UserRepo) :data:order (OrderRepo) ``` ### settings.gradle.kts ```kotlin include(":app") include(":feature:home", ":feature:profile", ":feature:order") include(":core:ui", ":core:network", ":core:database", ":core:common") include(":data:user", ":data:order") ``` ### feature module — build.gradle.kts ```kotlin plugins { alias(libs.plugins.android.library) alias(libs.plugins.kotlin.android) alias(libs.plugins.hilt) alias(libs.plugins.ksp) } android { namespace = "com.example.feature.profile" compileSdk = 34 defaultConfig { minSdk = 26 } } dependencies { implementation(project(":core:ui")) implementation(project(":core:common")) implementation(project(":data:user")) // feature 끼리 의존 X implementation(libs.compose.foundation) implementation(libs.hilt.android) ksp(libs.hilt.compiler) } ``` ### Convention plugin — 중복 제거 ```kotlin // build-logic/src/main/kotlin/AndroidFeatureConventionPlugin.kt class AndroidFeatureConventionPlugin : Plugin { override fun apply(target: Project) = with(target) { pluginManager.apply("com.android.library") pluginManager.apply("org.jetbrains.kotlin.android") pluginManager.apply("com.google.dagger.hilt.android") extensions.configure { compileSdk = 34 defaultConfig { minSdk = 26 } compileOptions { ... } } dependencies { "implementation"(project(":core:ui")) "implementation"(project(":core:common")) } } } // feature/profile/build.gradle.kts plugins { id("android.feature") } ``` ### app — composition root ```kotlin @HiltAndroidApp class App : Application() // AppNav.kt @Composable fun AppNav(nav: NavHostController) { NavHost(nav, startDestination = HomeRoute) { homeGraph(nav) // feature:home extension profileGraph(nav) // feature:profile extension orderGraph(nav) // feature:order extension } } ``` 각 feature 가 NavGraphBuilder extension 을 export. ### Build cache + parallel ```properties # gradle.properties org.gradle.parallel=true org.gradle.caching=true org.gradle.configureondemand=true android.enableJetifier=false kotlin.incremental.useClasspathSnapshot=true ``` ## 🤔 의사결정 기준 | 앱 크기 | 모듈화 | |---|---| | <10 화면 | 단일 모듈 OK | | 10-30 | feature 별 분리 시작 | | 30+ | 4계층 fully modular | | 팀 다수 | 명확 ownership boundary 로 모듈 | | Dynamic delivery | feature module = on-demand download | ## ❌ 안티패턴 - **feature ↔ feature 직접 의존**: cyclical 가능. app 또는 navigation contract 통해서. - **core 가 feature import**: 역방향. core 는 가장 아래. - **거대 :common 에 모든 것**: 어떤 변경도 모든 모듈 재빌드. 잘게 분리. - **모듈마다 다른 minSdk / compileSdk**: 일관성 깨짐. convention plugin. - **각 모듈에 같은 dependency 중복 선언**: convention plugin 으로. - **순환 의존 발견 늦음**: gradle build 시 즉시 실패. 의존 그래프 시각화 (Gradle Dependency Insight). - **feature module 이 직접 Application class 참조**: app 의존성 역방향. ## 🤖 LLM 활용 힌트 - 신규 Android = 4계층 modular 출발. - convention plugin 으로 boilerplate 제거. - feature 끼리는 NavGraphBuilder extension 으로만 통신. ## 🔗 관련 문서 - [[Android_Hilt_DI_Patterns]] - [[Android_Navigation_Compose]] - [[DevOps_Monorepo_Patterns]]