안드로이드 아키텍처 용어 사전
모듈화feature 모듈

멀티 모듈

앱을 여러 Gradle 모듈로 쪼개는 것. 빌드 속도와 경계 강제를 얻는다.

앱을 여러 Gradle 모듈로 쪼개는 것.

무엇이 무너지길래

단일 모듈이 커지면

  • 한 줄만 고쳐도 전체를 다시 컴파일한다 → 빌드가 몇 분씩
  • 아무 클래스나 참조할 수 있어 경계가 없다 → 스파게티
  • 팀이 같은 파일을 동시에 고쳐 충돌한다

왜 빌드가 빨라지나

  • 증분 컴파일 — 바뀐 모듈과 그것에 의존하는 모듈만 다시 컴파일한다

  • 병렬 컴파일 — 의존 관계가 없는 모듈은 동시에 컴파일된다

  • 빌드 캐시 — 안 바뀐 모듈의 결과를 재사용한다

  • 단, 모든 모듈이 core 하나에 의존하면

    • core 를 고칠 때마다 전부 다시 컴파일된다
    • 이득이 사라진다. core 를 더 잘게 나눠야 한다

경계를 강제한다

// internal 은 같은 모듈 안에서만 보인다
internal class UserRepositoryImpl : UserRepository   // 밖에서는 인터페이스만 보인다

단일 모듈에서는 internal이 의미가 없다 — 모듈이 나뉘어야 캡슐화가 컴파일러 수준에서 강제된다.

구조 — feature와 core

  • :app — 조립만 한다 (네비게이션 · DI 설정)
  • :feature:home
  • :feature:profile — 화면 단위. 서로를 참조하지 않는다
  • :core:data — Repository 구현
  • :core:domain — 모델 · UseCase
  • :core:network — Retrofit
  • :core:database — Room
  • :core:ui — 공용 컴포넌트 · 테마

by feature vs by layer

  • by layer — :ui :domain :data

    • 기능 하나를 고치려면 세 모듈을 전부 건드린다
    • 팀이 같은 모듈에서 충돌한다
  • by feature :feature:home — :feature:profile

    • 기능 하나의 변경이 한 모듈 안에서 끝난다
    • 팀이 모듈 단위로 나뉜다
    • 나중에 기능을 통째로 떼어내기 쉽다
  • feature 안에서 다시 계층을 나누는 것이 실무의 조합이다

implementation vs api

dependencies {
    implementation(project(":core:network"))   // 이 모듈만 쓴다. 밖으로 안 새어 나간다
    api(project(":core:domain"))               // 나를 쓰는 모듈에도 노출된다
}

implementation 을 쓰면

  • 이 의존성이 바뀌어도 나를 쓰는 모듈은 다시 컴파일되지 않는다
  • 빌드가 빨라진다

기본은 implementation, 공개 API 시그니처에 등장할 때만 api

언제 하지 말까

  • 혼자 만드는 작은 앱

  • 화면이 열 개 미만

  • 아직 도메인 경계가 안 정해졌다

  • 모듈 경계를 잘못 그으면 되돌리는 비용이 크다

    • 먼저 패키지로 나눠 보고, 경계가 굳으면 모듈로 승격한다

면접 함정

  • "모듈이 많을수록 빠르다" → 모듈 간 의존이 얽히면 오히려 느려진다. Gradle 설정 오버헤드도 있다.
  • "멀티 모듈은 대규모 앱만" → 규모보다 경계가 명확한가가 기준이다.

모듈 간 화면 이동을 어떻게 하나

문제: :feature:home 이 :feature:profile 을 모르는데 어떻게 이동하나

  • 방법 1 — 라우트 문자열만 공유한다

    • :core:navigation 에 "profile/{id}" 상수를 두고 :app 이 실제 화면을 등록한다
  • 방법 2 — 인터페이스를 공유한다

    • :core:navigation 에 interface ProfileNavigator
    • :feature:profile 이 구현하고 Hilt 가 연결한다

어느 쪽이든 feature 끼리는 직접 참조하지 않는다

빌드 설정 중복을 어떻게 줄이나

// buildSrc 또는 build-logic 에 컨벤션 플러그인을 만든다
plugins { id("myapp.android.feature") }   // compileSdk · 코틀린 옵션 · 공통 의존성이 한 줄로

모듈이 20개인데 각 build.gradle.kts 에 같은 설정이 복붙돼 있으면 compileSdk 를 올릴 때 20군데를 고쳐야 한다

  • 컨벤션 플러그인이 실질적으로 필수가 되는 지점

얼마나 빨라지나

  • 전체 빌드는 오히려 조금 느려질 수 있다 (모듈 설정 오버헤드)

  • 이득은 '한 파일 고치고 다시 빌드' 에서 나온다

    • 단일 모듈: 전체 재컴파일
    • 멀티 모듈: 그 모듈 + 의존하는 모듈만
  • 그래서 core 를 자주 고치는 구조면 이득이 거의 없다

  • core 를 안정적으로 유지하는 것이 모듈화 성패를 가른다

함께 보면 좋은 용어

노트에서 맥락과 함께 보기 — 멀티 모듈화 — 빌드 속도·경계·재사용