안드로이드 아키텍처 학습 노트 목차

멀티 모듈화 — 빌드 속도·경계·재사용

단일 모듈이 커지면 무엇이 무너지나

앱을 처음 만들 땐 *모듈이 하나(:app)*다. 모든 코드가 한 모듈에 있으니 편하다. 그런데 코드가 수만 줄, 화면 수십 개로 불어나면 세 가지가 동시에 무너진다.

첫째, 빌드가 느려진다. Gradle은 한 모듈 안에서 한 줄만 고쳐도 그 모듈 전체를 다시 컴파일해야 한다. 단일 모듈이면 어디를 고치든 앱 전체가 재컴파일된다 — 작은 변경에도 빌드가 수십 초~수 분씩 걸려 개발 리듬이 끊긴다. 둘째, 경계가 흐려진다. 한 모듈 안에선 어떤 클래스든 서로 자유롭게 참조할 수 있다. 그래서 시간이 지나면 UI 코드가 DB를 직접 만지고, 데이터 코드가 화면을 참조하는 식으로 의존성이 거미줄처럼 엉킨다 — 한 곳을 고치면 엉뚱한 데가 깨진다. 셋째, 팀이 충돌한다. 여럿이 같은 모듈을 건드리니 머지 충돌이 잦다.

멀티 모듈화는 코드를 여러 Gradle 모듈로 쪼개 이 셋을 한꺼번에 푼다 — 빌드는 바뀐 모듈만 재컴파일해 빨라지고, 모듈 경계가 의존성을 강제해 거미줄을 막으며, 팀이 서로 다른 모듈을 충돌 없이 개발한다. 구글의 Now in Android가 이 구조의 살아있는 모범이다. (출처: Android — Guide to Android app modularization.)

빌드가 왜 빨라지나 — 증분·병렬 컴파일

Gradle은 모듈 단위로 컴파일 결과를 캐시한다. 그래서 멀티 모듈에선 — 내가 :feature:detail만 고치면, 그 모듈과 그것에 의존하는 모듈만 다시 빌드하고, 건드리지 않은 :feature:home·:core:network는 캐시를 재사용한다(증분 빌드). 게다가 서로 의존하지 않는 모듈들은 동시에(병렬) 빌드된다 — CPU 코어를 다 쓴다.

다이어그램 로딩 중…

단일 모듈에선 이 두 이점이 전혀 없다(전체가 한 덩어리라 항상 전체 재컴파일). 큰 앱일수록 멀티 모듈의 빌드 시간 절감은 극적이다 — 이게 멀티 모듈을 도입하는 가장 현실적인 이유다.

경계를 강제한다 — internal로 구현을 감춘다

빌드 속도만큼 중요한 게 경계다. 모듈로 나누면 — 다른 모듈을 쓰려면 build.gradle에 의존성을 명시적으로 선언해야 하고, 선언하지 않은 모듈의 코드는 아예 보이지 않는다. 또 [andkt]의 internal 가시성이 모듈 단위로 작동해 — internal로 선언한 클래스는 그 모듈 안에서만 보이고 다른 모듈엔 숨는다.

// :core:network 모듈
internal class OkHttpFactory { ... }      // 모듈 내부 구현 — 밖에서 안 보임
class ApiClient(...) { ... }              // 공개 API — 다른 모듈이 이것만 쓴다

이렇게 공개할 것(public)과 감출 것(internal)을 모듈 경계에서 강제하면 — 다른 모듈은 내가 정한 공개 API로만 내 모듈을 쓸 수 있다. 단일 모듈의 "아무 데서나 아무거나 참조"가 원천 차단되어, 내부 구현을 자유롭게 바꿔도 다른 모듈이 안 깨진다.

모듈 구조 — feature와 core

흔한 구조는 *화면 기능별 :feature:* + 공통 :core:**로 나눈다.

다이어그램 로딩 중…
  • :app — 모든 feature를 조립하는 진입점. 로직을 거의 안 두고 가장 얇게 유지한다.
  • :feature:*화면 단위 기능(home·detail). 핵심 규칙은 — feature끼리 서로 직접 의존하지 않는다. home이 detail을 직접 부르면 둘이 엉키므로, 이동이 필요하면 navigation을 통해 간접 연결한다.
  • :core:* — 여러 feature가 공유하는 공통(:core:data·:core:network·:core:database·:core:domain·:core:ui 디자인 시스템).

여기서 절대 규칙은 — feature는 core에 의존하고, core끼리는 위→아래로만 의존하며, 순환 의존은 금지다. A가 B를, B가 A를 의존하면 Gradle이 빌드 순서를 정할 수 없어 빌드가 실패한다. 의존성 그래프는 반드시 *비순환(DAG)*이어야 한다.

by feature vs by layer — 왜 feature별인가

모듈을 기능(feature)별로 나눌지 계층(layer)별(전부 :ui, 전부 :data)로 나눌지 고민이 된다. 현대 권장은 feature별 + 공통 core다. 순수 계층별로 나누면 — "상세 화면" 하나를 고치려고 :ui·:domain·:data 세 모듈을 다 건드려야 해서, 변경이 여러 모듈로 흩어지고 빌드 캐시 이점도 줄어든다. 반면 feature별로 나누면 "상세 화면 작업은 :feature:detail 한 모듈에서" 끝난다. 대신 feature 모듈 안은 다시 ui/domain/data 패키지(또는 하위 모듈)로 나눠 계층을 지킨다 — 모듈 밖은 feature로, 모듈 안은 layer로가 균형점이다.

Gradle — implementation vs api

모듈 의존성을 선언할 때 *implementationapi*의 차이가 빌드 속도를 가른다.

// :feature:detail 의 build.gradle.kts
dependencies {
    implementation(project(":core:domain"))   // 감춰진 의존 — 전이되지 않음
    api(project(":core:ui"))                   // 전이 노출 — 이 모듈을 쓰는 쪽도 :core:ui를 봄
}

implementation으로 선언하면 — 그 의존성이 모듈 밖으로 전이되지 않는다. 즉 :feature:detail:core:domain을 implementation으로 쓰면, :feature:detail을 쓰는 다른 모듈은 :core:domain을 보지 못한다. 이게 왜 빌드를 빠르게 할까? — :core:domain의 내부가 바뀌어도, 그것을 implementation으로 감춘 모듈을 쓰는 쪽은 재컴파일할 필요가 없다(공개 API가 안 바뀌었으면). 반대로 api전이 노출이라 변경이 더 멀리 퍼진다. 그래서 **기본은 implementation, 정말 공개 API로 함께 내보내야 할 때만 api**를 쓴다.

모듈이 수십 개가 되면 각 build.gradle의 설정이 중복된다. 이때 **컨벤션 플러그인(convention plugin)**으로 공통 빌드 로직(코틀린 버전·컴파일 옵션·공통 의존성)을 한 곳(build-logic)에 모아 각 모듈이 id("myapp.android.feature") 한 줄로 가져다 쓴다(Now in Android의 패턴).

언제 멀티 모듈을 하지 말까

멀티 모듈은 공짜가 아니다모듈 설정·의존성 그래프 관리·내비게이션 추상화라는 초기 비용이 든다. 화면 몇 개짜리 작은 앱엔 오히려 과한 복잡도다. 그래서 — 앱이 커질 게 분명하거나 여러 팀이 붙는 경우에 도입하고, 작은 앱은 단일 모듈 + 패키지로 계층 분리만으로 충분하다. 다만 나중에 쪼개기 쉽게 처음부터 패키지를 feature/layer로 깔끔히 나눠 두면 전환이 수월하다.

정리 — 쪼개면 빠르고 단단해진다

멀티 모듈의 핵심 — feature + core로 쪼개 빌드를 빠르게(증분·병렬 컴파일), 모듈 경계와 internal로 의존성을 강제(구현 은닉), 공통을 core로 재사용, 순환 의존 금지(DAG), implementation으로 의존성을 감춰 변경 전파를 줄임, convention plugin으로 빌드 로직 공유. 작은 앱엔 과하지만 커질 앱·여러 팀빌드 시간과 구조 건강을 지키는 핵심 투자다.

한 줄 요약 — 멀티 모듈=빌드 속도(증분·병렬)+경계 강제(internal·명시 의존)+재사용. 구조: :app(조립)·:feature:(화면, 서로 직접 의존 X·navigation으로 연결)·:core:(공통). 순환 금지(DAG). Gradle은 implementation(전이 차단·빌드 가속)>api(전이 노출), convention plugin으로 빌드 로직 공유. 작은 앱엔 과함.

(출처: Android — Guide to Android app modularization / Common modularization patterns / Now in Android build-logic.)

의존성 주입 — Hilt·Koin상태 관리 — 호이스팅·SavedStateHandle·이벤트