[Spring] Gradle dependency - implementation과 api 차이

들어가기

한 프로젝트 안에 멀티모듈을 설정하면서 implementation에 대해 알아볼 필요가 있다고 생각했다.

그래서 이번에는 Gradle dependency 중에 implementation을 알아보고 api의 차이를 정리해보려고 한다. 

 

build script의 dependecies 블록 내에서 implementation, api 등과 같은 여러 종속 항목 구성 중 하나를 사용하여 라이브러리 종속 항목을 선언할 수 있다.

그 다양한 항목 중에서 자주 쓰인다는 implementation과 api를 차이를 살펴보자. 

 

Gradle 종속성 관리

Gradle 프로젝트에 종속성을 추가하면 Gradle은 해당 정보를 사용하여 두 가지 주요 클래스 경로를 관리한다고 한다.

  1. 컴파일 클래스(Compilation Classpath):  프로젝트를 컴파일하는 데 필요한 모든 종속성이 포함된다. 이는 소스코드 내에서 직접 참조되는 클래스나 메서드등을 찾기 위해 사용되며, 컴파일 타임에 코드에서 인식해야 하는 라이브러리를 말한다.
  2. 런타임 클래스 (Runtime Classpath): 프로젝트를 실행하는 데 필요한 모든 종속성이 포함된다. 이 클래스 경로는 컴파일 클래스 경로와 유사하지만 컴파일 시간이 아닌 런타임에만 필요한 추가 리소스나 라이브러리(예: JDBC 드라이버, 인터페이스의 런타임 구현)를 포함할 수 있다.

 

implementation

컴파일 시간 implementation 으로 선언된 의존성은 해당 모듈의 컴파일 시간 클래스패스에 포함된다.

이는 모듈 내부에서 의존성을 사용할 수 있음을 의미하지만, 이 모듈에 의존하는 다른 모듈은 이 의존성에 접근할 수는 없다. 즉, 다른 모듈에서는 이 의존성을 컴파일 시점에 사용할 수 없는 것이다.

 

런타임 implementation 의존성은 해당 의존성이 런타임에 필요한 라이브러리나 프레임워크의 일부인 경우, 최종 실행 가능 파일(예: bootJar)에 포함된다. 하지만, 이 의존성을 의존하는 다른 모듈에서 직접적으로 접근하여 사용할 수 있는 것은 아니다. 런타임에 해당 모듈의 코드가 필요한 작업을 수행할 때만 내부적으로 사용된다.

 

중요한 건 implementation 의존성의 주된 목적은 모듈 내부에서만 사용되고, 해당 모듈을 의존하는 다른 모듈에는 노출되지 않도록 한다는 점이다.

 

장점

  • 연결된 dependency가 많이 줄어들고, 수정을 하더라도 재빌드를 적게 하니 소요 시간도 적으며 빠르다.
  • API의 노출을 막아준다. api를 사용한다면 연결된 API가 모두 노출(exposed)되지만 implementation은 노출되지 않는다. API가 노출되면 API로직에서 유효성 검사 및 원치 않는 데이터가 들어와 뜻하지 않게 보안의 위협이 발생한다고 한다.  

api

api 종속성을 포함하면 다른 모듈에 그 종속성을 의도적으로 내보내기를 원한다는 뜻으로 해당 의존성을 사용하면 모듈을 의존하는 다른 모듈로 전파된다.

그렇기에 api는 특히 라이브러리나 공통 모듈을 개발할 때 유용하며, 모듈 간의 API를 공유하고자 할 때 사용된다.

 

주의해서 사용해야하며 다른 업스트림소비자에게 일시적으로 내보내는 종속성만 함께 사용해야한다.

(업스트림(upstream)은 클라이언트나 로컬 기기(일반적으로 컴퓨터나 모바일기기)에서 서버나 원격 호스트(이하 서버)로 보내지는(전송되는) 데이터 또는 보내는 것을 의미한다.)

 

그 이유는 api 종속성이 외부 API를 변경하면 Gradle이 컴파일 시에 해당 종속성에 엑세스할 권한이 있는 모듈을 모두 다시 컴파일하기 때문이다. 그렇기에 api 종속성이 많이 있으면 빌드 시간이 상당히 증가한다. 종속성의 API를 별도의 모듈에 노출시키고 싶은 것이 아니라면 라이브러리 모듈은 implementation 종속성을 대신 사용해야 한다.

 

사용 시 고려사항

  • 의존성 전파: 앞서 말했듯 api 의존성은 모듈 간의 의존성 전파를 가능하게 한다. 이는 모듈이 제공하는 API의 일부로서 다른 모듈에 의존성을 노출시킬 때는 유용하다. 하지만 모든 의존성을 api로 선언하는 것은 당연히 피해야 한다. 불필요하게 많은 의존성이 전파되면 프로젝트의 컴파일 시간이 증가하고, 의존성 관리가 복잡해질 수 있기 때문이다.
  • 모듈 간 결합도: api 의존성을 사용하면 모듈 간의 결합도가 증가할 수 있다. 모듈 A의 API가 변경되면, 이 API를 사용하는 모든 모듈 B, C 등도 영향을 받을 수 있으므로, API로 의존성 변경 시 주의가 필요하다.

 

implementation vs api

출처 - https://bluayer.com/13

위 그림을 참고해서 설명하면 

implementation (의존성 전파 X)

- 의존 라이브러리 수정 시 해당 모듈까지만 재빌드한다.

-  A(implementation) <- B <-C 일때,  C에서는 A를 접근할 수 없다. 

-  A라는 모듈을 수정하면 해당 모듈(A)을 직접적으로 의존하고 있는 모듈(B)만 재빌드한다. 

 

api (의존성 전파 O)

- 의존 라이브러리 수정 시 해당 모듈을 의존하고 있는 모듈들까지 재빌드

- A(api) <- B <- C 경우에서 일때, C에서 A를 접근할 수 있다. 또한 A 수정 시에는 B와 C 모두 재빌드한다.

- A라는 모듈을 수정 하면 해당 모듈(A)를 직,간접적으로 의존하고 있는 모듈들(B,C) 모두 재빌드한다. 

 


정리

  • implementation과 api의 큰 차이는 의존성 전파의 유무이다. implementation은 의존성 전파를 하지 않고 api는 전파한다. 
  • implementation 의존성은 해당 의존성을 선언한 모듈 내에서만 사용되며, 해당 모듈을 의존하는 다른 모듈로는 의존성이 전파되지 않는다. 의존성을 다른 모듈로 전파하려면 api 의존성을 사용할 수 있지만, 꼭 필요한 상황이 아닌 이상은 권장하지는 않는다.

 


참고 자료

안드로이드 개발자 링크

https://developer.android.com/studio/build/dependencies?utm_source=android-studio&hl=ko#dependency_configurations 

 

 

빌드 종속 항목 추가  |  Android 개발자  |  Android Developers

Android 스튜디오에서 Gradle 빌드 시스템을 이용하여 빌드 종속성을 추가하는 방법에 관해 알아보세요.

developer.android.com

Gradle 공식 문서 https://docs.gradle.org/current/userguide/dependency_management_for_java_projects.html#sec:configurations_java_tutorial

 

Managing Dependencies of JVM Projects

How does Gradle know where to find the files for external dependencies? Gradle looks for them in a repository. A repository is a collection of modules, organized by group, name and version. Gradle understands different repository types, such as Maven and I

docs.gradle.org