* 모듈, 라이브러리, 디펜던시 등의 용어가 많이 나오는데, 이 포스팅에서는 세 가지 용어 모두 같은 의미로 사용되었음
api
: api로 정의한 dependency는 우리 어플리케이션의 binary interface에 포함되는 디펜던시이다. 따라서 우리 라이브러리를 사용하는 consumer 쪽의 compile classpath에도 이 디펜던시가 추가된다.
implementation
: implementation으로 정의한 dependency는 우리 어플리케이션의 binary interface에 포함되지 않는 디펜던시이다. 따라서 우리 라이브러리를 사용하는 consumer 쪽의 compile classpath에는 이 디펜던시가 추가되지 않는다.
cf) classpath 확인하는 커맨드 :
./gradlew dependencies --configuration compileClasspath
./gradlew dependencies --configuration runtimeClasspath
consumer의 classpath에 추가한다는 말의 의미?
consumer 모듈 쪽에서도 해당 라이브러리의 공개된 public 메서드, 모델들을 마음껏 쓸 수 있다는 의미.
A -> B -> C 일 때 B -> C에서 의존성 설정이 api이면 A에서도 C를 쓸 수 있고, implementation이면 A에서는 C를 사용할 수 없다.
이 두가지 옵션 중 잘 선택하면 1. 컴파일 횟수를 줄일 수 있고, 2. 굳이 노출하지 않아도 될 종속성을 숨겨줄 수 있다.
=> 이때 착각하지 말아야할 것은, 해당 설정은 우리 라이브러리의 컴파일 속도를 빠르게하는 것이 아니라, 우리 라이브러리를 사용할 consumer 쪽을 배려해주고, 이들에게 필요한 정도의 정보만 제공하고 캡슐화하는 개념이다. (이 개념을 제대로 설명해주는 곳이 없어서 처음에 너무 헷갈렸다...)
(A로 갈수록 상위 모듈, C로 갈수록 하위 모듈)
예를 들어 위 그림과 같이 A 모듈을 B 모듈이 의존(사용)하고, B 모듈에 C모듈이 의존하고 있는 상황일 때,
gradle에서 api를 이용해 의존성 주입을 해줄 경우, A 모듈의 변화가 B, C의 rebuild를 야기한다.
반면, implementation을 이용하면 직접적으로 의존하고 있는 B 모듈만이 rebuild 된다.
(rebuild 되는 건 당연하다. 그 모듈을 직접적으로 사용하고 있고, classpath에도 추가되어있기 때문에..!)
당연히 implementation이 api에 비해 가벼운 옵션이기 떄문에, 우리 프로젝트가 라이브러리로 제공되었을 때에 우리가 사용한 라이브러리가 인터페이스(api..)를 통해 하위 모듈에 노출되는 경우라면 api, 그 외에 내부적으로만 이용하는 경우라면 implementation을 쓰는 것이 좋다.
testImplementation
: 테스트 파일에서만 사용하는 의존성들에서 사용
runtimeOnly
: runtime 시에만 필요한 라이브러리인 경우
compileOnly
: compile 시에만 빌드하고 빌드 결과물에는 포함하지 않는다.
runtime 시 필요없는 라이브러리인 경우 (runtime 환경에 이미 라이브러리가 제공되고 있는 등의 경우)
annotationProcessor
: annotation processor 명시 (ex:lombok)
참고 자료
gradle 공식 문서
https://cantcoding.tistory.com/59
https://compogetters.tistory.com/64
https://www.youtube.com/watch?v=K96rDeiKT-g