반응형
대형 Compose 앱을 설계할 때는 모듈 분리(멀티 모듈)과 Clean Architecture를 결합하면 유지보수성과 빌드 속도, 테스트 용이성이 크게 개선됩니다. 이번 글에서는 권장 모듈 구조, Gradle 설정 팁, 의존성 주입(예: Hilt) 적용 방식, 모듈 간 경계 규칙과 테스트 전략까지 실무 관점에서 정리합니다.
1. 왜 멀티 모듈 + Clean Architecture인가?
- 빌드 시간 단축: 변경 범위를 작은 모듈로 제한하면 빌드/증분 컴파일이 빨라집니다.
- 명확한 책임 분리: presentation / domain / data 레이어가 물리적으로 분리되어 코드 품질 향상.
- 재사용성 증가: 공통 UI, 유틸, 네트워크 모듈을 여러 앱에서 공유 가능.
- 테스트 편의성: 작은 단위 모듈을 독립적으로 단위 테스트/통합 테스트 수행.
2. 권장 모듈 구조
일반적인 모듈 구조 예시는 아래와 같습니다.
/app // Android application 모듈 (Compose 호스트, DI 초기화)
/build.gradle.kts
/domain // 순수 비즈니스 로직 (플랫폼 비의존)
/build.gradle.kts
/src/main/java/... // UseCase, Entity, Repository 인터페이스
/data // 구현체 (Retrofit, Room 등) - domain의 Repository 인터페이스 구현
/build.gradle.kts
/src/main/java/... // ApiService, Dao, RepositoryImpl
/ui-common // 재사용 가능한 Compose 컴포넌트, 테마, 스타일
/build.gradle.kts
/src/main/java/... // Buttons, ListItems, Theme
:feature- // 기능별(Feature) 모듈 - presentation + viewModel
/build.gradle.kts
/src/main/java/... // Compose 화면, ViewModel (domain에 의존)
3. 의존성 규칙 (권장)
- app → feature 모듈, ui-common
- feature → domain, ui-common
- data → domain (구현체 제공)
- domain은 다른 모듈에 의존하지 않음 (플랫폼/프레임워크 독립)
이 규칙을 지키면 순환 의존성을 피하고 각 모듈의 책임이 명확해집니다.
4. Gradle 설정 (Kotlin DSL 예)
각 모듈의 build.gradle.kts에서 api/implementation 의존성을 명확히 관리하세요.
plugins {
id("com.android.library")
kotlin("android")
}
android {
compileSdk = 34
// ...
}
dependencies {
api(project(":domain")) // domain을 외부에 노출할 필요가 있는 경우만 api
implementation(project(":ui-common"))
implementation("androidx.compose.ui:ui:1.5.0")
// feature 모듈은 domain 인터페이스에 의존
}
5. DI (Hilt) 적용 요령
Hilt를 사용하면 모듈 간 구현 바인딩이 쉬워집니다. 중요한 점은 바인딩을 data 모듈에서만 수행하고, domain은 인터페이스만 제공하는 것입니다.
@Module
@InstallIn(SingletonComponent::class)
object NetworkModule {
@Provides
fun provideApiService(retrofit: Retrofit): ApiService = retrofit.create(ApiService::class.java)
}
@Module
@InstallIn(SingletonComponent::class)
abstract class RepositoryModule {
@Binds
abstract fun bindUserRepository(impl: UserRepositoryImpl): UserRepository // UserRepository는 domain에 정의된 인터페이스
}
그리고 App 모듈은 Hilt Android Application을 선언해서 DI를 초기화합니다.
6. Compose + Navigation + 모듈 라우팅
각 Feature 모듈은 자체적으로 NavGraph를 제공하고 App 모듈에서 조합하는 방식이 좋습니다.
// feature-profile 모듈
@Composable
fun ProfileNavGraph(navController: NavHostController) {
NavHost(navController, startDestination = "profile/main") {
composable("profile/main") { ProfileScreen() }
}
}
// app 모듈에서 통합
NavHost(navController, startDestination = "home") {
composable("home") { HomeScreen() }
navigation(startDestination = "profile/main", route = "profile") {
// feature 모듈에서 NavGraph 제공하는 helper를 호출하거나, route만 연결
}
}
7. 테스트 전략
- domain 유닛 테스트: UseCase, 도메인 로직 검증 (Mockito/MockK / kotest)
- data 단위 테스트: MockWebServer로 API 응답 시뮬레이션
- feature 통합 테스트: ViewModel + fake repository 로 UI 상태 검증
- CI: 변경된 모듈만 테스트/빌드하도록 설정하면 효율적
8. 빌드 성능 최적화 팁
- 불필요한 annotation processor 사용 줄이기 (kapt 대상 최소화)
- 공용 의존성은 버전 충돌을 피하기 위해
libs.versions.toml로 관리 - gradle.properties에
org.gradle.parallel=true,kotlin.incremental=true설정 - 큰 모듈은 기능별로 쪼개기 (feature 모듈 분리)
9. 장단점 요약
장점
- 유지보수성과 협업 생산성 향상
- 테스트가 쉬워지고 빌드 시간이 향상될 수 있음
- 모듈 재사용(라이브러리화) 가능
단점 / 주의사항
- 초기 설계 난이도 증가 — 모듈 경계 설계가 중요
- 프로젝트가 작은 경우 오버엔지니어링이 될 수 있음
- 모듈간 버전/의존성 관리가 추가로 필요
10. 시작 체크리스트
- 도메인/데이터/프레젠테이션 책임을 문서화
- 공통 UI 모듈(ui-common) 설계 (테마, 컴포넌트)
- DI 바인딩은 data에서만 구현, domain은 인터페이스 유지
- Gradle 의존성 규칙을 린트로 강제 (detekt, gradle-lint-plugin 등)
- CI 파이프라인에서 모듈 단위 빌드/테스트 단계 구성
마무리: 멀티 모듈과 Clean Architecture를 적용하면 대규모 Compose 앱의 확장성과 유지보수성이 크게 개선됩니다. 처음에는 설계 비용이 들지만, 팀과 코드베이스가 커질수록 그 이점이 명확해집니다. 원하시면 이 구조로 실제 샘플 프로젝트의 Gradle 설정과 디렉터리 생성 스크립트도 작성해드릴게요.
반응형
'개발일기' 카테고리의 다른 글
| OkHttp Interceptor로 공통 로깅 및 에러 처리하기 (1) | 2025.09.22 |
|---|---|
| Compose + Paging 3로 무한 스크롤 구현하기 (3) | 2025.09.18 |
| 안드로이드 네트워크 + 로컬 캐싱 전략 (Room + Flow) (1) | 2025.09.11 |
| 안드로이드 에러 처리 및 UI 피드백 전략 (2) | 2025.09.10 |
| Jetpack Compose와 네트워크 계층 연결하기 (2) | 2025.09.09 |