본문 바로가기

Android/Gradle

[Gradle] Implementation vs Api

728x90

Gradle에서 dependencies 블록에서는 implementation, annotationProcessor, api, lintChecks 등 다양한 키워드를 사용하여 종속 항목을 추가할 수 있다.

 

그중, 사용 용도가 비슷하여 헷갈릴 수 있는 api와 implementation의 차이점에 대하여 알아보고자 한다.


안드로이드 공식 페이지에서 API와 Implementation에 대하여 확인해보면 다음과 같이 나와있다.

 

  • implementation
Gradle이 컴파일 클래스 경로에 종속 항목을 추가하고 빌드 출력에 종속 항목을 패키징 합니다. 그러나 모듈에서  implementation 종속 항목을 구성하면 모듈이 컴파일 시간에 종속 항목을 다른 모듈에 누출하기를 바라지 않는다는 것을 Gradle에 알려주는 것입니다. 즉 종속 항목은 런타임에만 다른 모듈에서 이용할 수 있습니다.
api 또는 compile(지원 중단됨) 대신 이 종속 항목 구성을 사용하면 빌드 시스템에서 다시 컴파일해야 하는 모듈 수가 줄어들기 때문에 빌드 시간이 크게 개선될 수 있습니다. 예를 들어 implementation 종속 항목이 API를 변경하면 Gradle은 이 종속 항목과 이에 직접적으로 종속된 모듈만 다시 컴파일합니다. 대부분의 앱과 테스트 모듈은 이 구성을 사용해야 합니다.
  • api
모듈에 api 종속 항목이 포함되면 모듈이 다른 모듈로 종속 항목을 이전하여 다른 모듈에서 런타임과 컴파일 시간에 사용할 수 있도록 한다는 것을 Gradle에 알려주는 것입니다.
이 구성은 compile(현재는 지원 중단됨)처럼 작동하지만 주의해서 다른 업스트림 소비자에게 이전해야 하는 종속 항목과만 사용해야 합니다. 그 이유는 api 종속 항목이 외부 API를 변경하면 Gradle이 컴파일 시 종속 항목에 액세스 권한이 있는 모든 모듈을 다시 컴파일하기 때문입니다. 따라서 api 종속 항목 수가 많으면 빌드 시간이 크게 증가할 수 있습니다.

 

항목에서 중요한 부분은 볼드처리해 두었다.

해당 내용을 보고 알 수 있는 부분은, implementation로 선언되는 경우 그 이하의 종속된 항목들을 사용할 수 없으며, api로 선언되는 경우 그 이하의 종속된 항목들을 사용할 수 있다.

 

이렇게 말로만 해서는 헷갈릴 수 있으므로, 간단하게 예제를 만들어서 확인해보았다.

 

 

모든 경우의 수를 한번에 테스트하기 위해서 A~H까지의 모듈을 만들어 보았다.

각 라이브러리에 선언된 클래스는 다음과 같이 되어있다.

class AModuleClass {
    fun callAModuleClass(): String {
        return "Call A Module Class"
    }
}

 

각 모듈에 하나의 클래스를 만들고, 위와 같이 String 값을 리턴하는 함수를 만들어두었다.

 

그 후, gradle에 다음과 같이 선언했다.

 

// a,b module implementation > implementation
implementation project(':amodule')

// c,d module api > api
api project(':cmodule')

// e,f module implementation > api
implementation project(':emodule')

// g,h module api > implementation
api project(':gmodule')

 

a,b 모듈은 모두 implementation으로 선언.

c,d 모듈은 모두 api로 선언.

e 모듈은 implementation, f 모듈은 api로 선언.

g 모듈은 api, h 모듈은 implementation로 선언.

 

이런 식으로 선언하여 각 라이브러리 모듈에서 선언한 함수를 사용할 수 있는지 확인해 보았다.

 

 

이렇게 사용이 안 되는 부분을 보고, 위의 api와 implementation의 차이점을 보면 쉽게 이해가 될 것이다.

 

 

App > 1Depth > 2Depth 구조로 생각하여 4가지 경우의 수를 확인해 보자.

 

  • App 모듈에서 implementation으로 1Depth(이하 D로 표기) 모듈을 추가, 1D에서도 2D를 implementation으로 추가
    • 1D의 클래스는 사용이 가능하다.
    • 2D의 클래스는 사용할 수 없다.
  • App 모듈에서 api로 1D 모듈을 추가, 1D에서도 2D를 api로 추가
    • 1D의 클래스와 2D의 클래스를 모두 사용할 수 있다.
  • App 모듈에서 implementation으로 1D 모듈을 추가, 1D에서는 api로 2D를 추가.
    • 1D의 클래스와 2D의 클래스를 모두 사용할 수 있다.
  • App 모듈에서 api로 1D 모듈을 추가, 1D에서는 implementation으로 2D를 추가
    • 1D의 클래스는 사용이 가능하다.
    • 2D의 클래스는 사용할 수 없다.

이렇게 보면, 2Depth의 모듈이 1Depth에서 implementation으로 선언된 경우 App 모듈에서 사용이 불가능하다.

 

 

내용을 보면 알 수 있겠지만, BModuleClass를 사용하기 위해서는 dependency를 추가해달라고 나온다.

A모듈에는 B모듈이 추가되어 있지만, A모듈만 추가한 App 모듈에서는 이를 알 수 없다는 것이다.

즉, 설명에서 나와있듯이 implementation은 종속된 항목들을 다른 모듈에 누출하기를 바라지 않기 때문에 직접 모듈이 추가된 경우에만 사용이 가능하고, 이하의 모듈에는 접근이 불가능하다.

 

라이브러리를 수정했을 때 빌드시간 측면에서 보면, implementation은 이하의 내용을 알 수 없기 때문에 실제로 영향 있는 부분인 바로 위 모듈까지만 재 빌드를 진행한다.

즉, App > 1D > 2D 모두 implementation으로 선언되어있는 경우, 2D가 수정되면 2D, 1D이 재 빌드되고 App은 재 빌드되지 않는다.

하지만, api는 위의 구조에서 App, 1D 모두 영향이 있기 때문에 2D가 수정되면 1D, App 모두 재 빌드 되어야 하기 때문에 모듈의 크기가 커지고, 뎁스가 많아질수록 빌드 시간이 크게 증가할 가능성이 있다.


compile이 deprecated되고, 지금까지는 별 다른 생각 없이 implementation만 사용해 왔다.

api 키워드가 있는 것은 알고있었지만 이것을 사용해야만 하는 상황이 없었고, 모듈을 디테일하게 작업해보지 않았기 때문에 사용해야 한다는 생각조차 하지 못했다.

 

물론, 해당 키워드가 어떠한 동작을 하는지 확인한 지금도 api 키워드를 자주 사용할까? 하면 그렇지는 않을 것 같다.

다중 모듈 구조에서 상호 참조에 대한 문제를 해결할 때 해결 방법 중 하나로 사용할 수 있다는 것을 제외하고는 implementation을 사용하는 게 더 좋을 것으로 생각된다.

 

하지만 아직까지 api 키워드를 사용해야 좋은 구조, 사용해야만 하는 구조를 접해보지 못했기 때문이라고 생각하고 앞으로 사용할 때 이 차이를 고려해서 한번 더 생각하고 종속 항목을 구성해야겠다.

728x90