모듈 의존성을 선언하는 두 가지 방식. 겉보기엔 같은 일을 하지만 변경이 퍼지는 범위가 다르다.
dependencies {
implementation(project(":core:domain")) // 감춘다 — 전이되지 않는다
api(project(":core:ui")) // 내보낸다 — 쓰는 쪽도 함께 본다
}
:feature:detail 이 :core:domain 을 implementation 으로 쓰면, :feature:detail 을 쓰는 다른 모듈은 :core:domain 을 보지 못한다.
왜 빌드 속도가 갈리나
핵심은 재컴파일 범위다.
api 로 선언 implementation 으로 선언
:core:domain 변경 :core:domain 변경
↓ ↓
:feature:detail 재컴파일 :feature:detail 재컴파일
↓ ↓
그것을 쓰는 모듈 전부 여기서 멈춘다
↓ (공개 API 가 안 바뀌었으면)
또 그것을 쓰는 모듈…
api 는 의존성을 공개 서명의 일부로 만든다. 그래서 바뀌면 그것을 보는 모든 모듈이 다시 컴파일된다. implementation 은 벽을 세워 파급을 그 모듈에서 끊는다. 모듈이 수십 개인 프로젝트에서 이 차이가 빌드 시간을 몇 배로 가른다.
그래서 기본은 implementation 이다
api 가 필요한 경우는 좁다 — 그 타입이 내 공개 API 에 등장할 때다.
// :core:ui 의 Theme 을 반환한다면 → 쓰는 쪽도 Theme 을 알아야 한다 → api
fun appTheme(): Theme
// :core:domain 의 UseCase 를 안에서만 쓴다면 → 감춰도 된다 → implementation
판단 기준은 취향이 아니라 "이 타입이 밖에서 보이는가" 다. 안 보이는데 api 로 두면 불필요한 재컴파일만 늘고, 보이는데 implementation 으로 두면 쓰는 쪽이 컴파일 에러를 만난다.
캡슐화의 도구이기도 하다
빌드 속도만의 문제가 아니다. implementation 은 다른 모듈이 내 내부 의존성에 손대지 못하게 막는다. 감추지 않으면 시간이 지나며 모두가 :core:domain 을 직접 참조하게 되고, 모듈 경계가 이름만 남는다. 언어의 접근 제어자가 클래스 안에서 하는 일을 모듈 수준에서 하는 셈이다.
나머지 두 가지
compileOnly("javax.annotation:jsr305:3.0.2") // 컴파일에만 필요, 패키징 안 함
runtimeOnly("com.h2database:h2:2.2.224") // 실행에만 필요, 컴파일 땐 안 보임
compileOnly— 애너테이션처럼 컴파일할 때만 필요하고 실행 중엔 없어도 되는 것. 결과물에 안 들어가 크기가 준다runtimeOnly— 드라이버처럼 코드에서 직접 부르지 않는데 실행 시 필요한 것. 컴파일 때 안 보이므로 실수로 직접 참조하는 것을 막아 준다
네 가지를 정리하면 축이 둘이다 — 컴파일에 필요한가, 그리고 밖으로 내보내는가.
어긋났는지 어떻게 아나
./gradlew :feature:detail:dependencies 로 의존성 트리를 보면 무엇이 전이되는지 드러난다.
빌드가 느려졌다면 한 모듈을 고쳤을 때 몇 개가 다시 컴파일되는지를 먼저 본다. 예상보다 넓게 퍼진다면 대개 어딘가에서 api 를 남발한 것이다. 특히 :core 계열이 api 로 열려 있으면 거의 모든 모듈이 그 변경을 따라 다시 컴파일된다 — 쪼갠 효과가 통째로 사라지는 지점이다.
설정 중복은 컨벤션 플러그인으로
모듈이 수십 개가 되면 각 build.gradle.kts 의 설정이 그대로 복사된다. 코틀린 버전을 올리면 수십 곳을 고쳐야 한다.
컨벤션 플러그인은 공통 빌드 로직을 build-logic 한 곳에 모으고, 각 모듈은 id("myapp.android.feature") 한 줄로 가져다 쓴다. 모듈 파일이 짧아지고 규칙이 한 곳에서 강제된다.
면접 함정
- ❌ "
api가 더 편하니 그것만 쓰면 된다" → 전이가 퍼져 재컴파일 범위가 커지고 모듈 경계가 무너진다. - ❌ "
implementation이면 런타임에도 안 보인다" → 컴파일 시점의 가시성 문제다. 실행 시에는 클래스패스에 있다. - ❌ "모듈을 쪼개면 무조건 빨라진다" →
api로 다 열어 두면 쪼갠 효과가 사라진다.