Android 멀티 모듈 아키텍처: Hilt로 구현하는 의존성 관리 전략

Android 애플리케이션의 규모가 커짐에 따라 모놀리식(Monolithic) 구조는 유지보수, 확장성, 그리고 팀 협업 측면에서 한계에 부딪힙니다. 멀티 모듈 아키텍처는 이러한 문제를 해결하기 위한 효과적인 접근 방식으로, 기능 또는 계층별로 프로젝트를 분리하여 코드 베이스의 응집도를 높이고 결합도를 낮춥니다. 본 글에서는 AndroidX Jetpack 생태계와 Hilt를 활용한 멀티 모듈 설계의 실제 구현 방법을 살펴봅니다.

모듈 분리 설계의 이점

멀티 모듈 구조의 핵심은 관심사의 분리입니다. 각 모듈은 독립적인 책임을 가지며, 명확한 인터페이스를 통해서만 상호작용합니다. 이를 통해 다음과 같은 이점을 얻을 수 있습니다:

  • 빌드 시간 단축: 변경된 모듈만 선택적으로 빌드할 수 있습니다.
  • 코드 재사용성 향상: 공통 로직을 별도의 라이브러리 모듈로 분리하여 여러 앱에서 재사용할 수 있습니다.
  • 팀 생산성 증가: 각 팀이 서로 다른 모듈을 담당하여 병렬 개발이 가능합니다.

실전 사례: HiltWithMultiModuleSimple 구조 분석

AndroidX-Jetpack-Practice 프로젝트 내의 HiltWithMultiModuleSimple 예제는 이러한 개념을 실제로 구현한 사례입니다. 이 프로젝트는 세 가지 주요 모듈로 구성됩니다:

  • common-core: 네트워크, 데이터베이스, 유틸리티 클래스 등 애플리케이션 전반에서 사용되는 핵심 인프라를 제공하는 베이스 모듈입니다.
  • feature-task: 특정 비즈니스 기능(예: 할 일 목록 관리)을 담당하는 피처 모듈입니다. common-core에 의존합니다.
  • app: 최종 실행 가능한 애플리케이션 모듈로, 위 두 모듈을 포함하여 전체 앱을 조립합니다.

이러한 계층적 구조는 의존성 방향을 명확히 하며, 순환 참조 문제를 방지합니다.

Gradle을 통한 모듈 의존성 정의

모듈 간의 관계는 프로젝트 루트의 settings.gradle.kts 파일에서 선언됩니다. 아래는 관련 설정의 예시입니다:

// settings.gradle.kts
rootProject.name = "MyMultiModuleApp"
include(":app")
include(":common-core")
include(":feature-task")

이렇게 포함된 각 모듈은 자신의 build.gradle.kts 파일에서 다른 모듈에 대한 의존성을 명시합니다. 예를 들어, feature-task 모듈은 다음과 같이 common-core를 참조합니다:

// feature-task/build.gradle.kts
dependencies {
    implementation(project(":common-core"))
}

Hilt를 활용한 의존성 주입 구현

멀티 모듈 환경에서 Hilt는 의존성 관리를 단순화합니다. 각 모듈은 자신에게 필요한 의존성을 제공하는 Hilt 모듈을 정의합니다. 예를 들어, common-core 모듈 내에 네트워크 관련 의존성을 제공하는 모듈을 생성할 수 있습니다:

// common-core/src/main/java/.../di/NetworkModule.kt
@Module
@InstallIn(SingletonComponent::class)
object NetworkModule {

    @Provides
    @Singleton
    fun provideOkHttpClient(): OkHttpClient {
        return OkHttpClient.Builder()
            .connectTimeout(30, TimeUnit.SECONDS)
            .build()
    }

    @Provides
    @Singleton
    fun provideRetrofit(client: OkHttpClient): Retrofit {
        return Retrofit.Builder()
            .baseUrl("https://api.example.com/")
            .client(client)
            .addConverterFactory(GsonConverterFactory.create())
            .build()
    }
}

feature-task 모듈의 ViewModel이나 Repository에서는 @Inject 어노테이션을 통해 이 의존성을 주입받아 사용합니다. Hilt의 컴파일 타임 검증 덕분에 런타임 오류를 사전에 방지할 수 있습니다.

모듈 간 인터페이스 설계 원칙

성공적인 멀티 모듈 아키텍처를 위해서는 모듈 간의 인터페이스(API) 설계가 중요합니다. 일반적으로 다음과 같은 원칙을 따릅니다:

  • 느슨한 결합: 직접 클래스에 의존하기보다 인터페이스나 추상 클래스에 의존합니다.
  • 명확한 경계: 공유되는 데이터 모델(DTO)과 서비스 인터페이스는 common-core와 같은 공통 모듈에 정의합니다.
  • 최소한의 노출: 모듈의 public API는 꼭 필요한 기능만 제공하고, 내부 구현은 숨깁니다.

이러한 설계를 통해 각 모듈의 진정한 독립성을 보장하고, 나중에 다른 모듈로 교체하거나 테스트하기 쉬운 구조를 만들 수 있습니다.

태그: Android AndroidX Jetpack Hilt Multi-Module

5월 28일 08:03에 게시됨