[Gradle] dependency api와implementation

unhak·2022년 11월 2일

Gradle

목록 보기
1/1
post-thumbnail

들어가며

오래된 Gradle 버전에는 implementation이 없어서 compile을 사용했고, 새로운 버전(6.x 이후 버전)에서 compile이 deprecated 되면서 compile 대신에 api 키워드가 사용되게 되었다. 이 때문에 오래된 프로젝트들의 build.gradle 파일들을 보면 api와 compile을 implementation 대신 쓰는 것을 볼 수 있다.


dependencies 선언 방법

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-web:2.2.5.RELEASE'
}

위 코드에서는 implementation 설정을 사용했지만 Gradle 6 버전 이하에서는 그냥 compile 설정을 사용할 수도 있다.

dependencies {
    compile 'org.springframework.boot:spring-boot-starter-web:2.2.5.RELEASE'
}

하지만 compile은 Deprecated되었고 컴파일 단계 디펜던시는 implementation을 사용하도록 바뀌었다.
compile 설정은 Gradle 7.0에서 삭제되었고 implementation 디펜던시 설정이 compile 디펜던시 설정과 같은 기능을 제공한다.
즉 항상 compile 대신 implementation을 사용하면 된다.


implementation dependency란?

자바를 building할때와 running할때 두개의 classpaths가 있어야 된다.

  1. Compile classpath - JDK가 java code를 .class files로 컴파일 할때 필요로 하는 의존 리스트이다.
  2. Runtime classpath - 컴파일된 자바 코드가 실제로 실행할때 필요로 하는 의존 리스트이다.

그래들에서 디펜던시 설정을 할때 사실 실제로 우리가 하는일은 어느 classpath에서 보여지게 할 것인지이다.

  • complieOnly - 컴파일 패스에만 설정
  • runtimeOnly - 런타임 패스에만 설정
  • implementation - 위 두개의 패스에 둘다 설정

런타임, 컴파일 패스 둘다 설정하려면 implementation을 사용하면 된다.
하지만 만약 아니라면 compileOnly나 runtimeOnly를 고려해봐야 한다.
그렇다면 왜 특정한 클래스패스 설정에 대해 이렇게 신경써야 할까? 그 이유는 약간의 이익 때문이다.

  • 빠른 컴파일속도 클래스패스에 적은 디펜던시가 있다.
  • 코드를 작성할때 실수로 원치않은 클래스 사용을 하지 않고 디펜던시 설정을 런타임 classpath에만 설정한다면 코드 작성할때 실수로 사용하는 것을 방지.
  • 클래스 패스 정리 복잡함 감소.

api와 implementation의 차이

dependency tree

다음과 같은 라이브러리 모듈이 있다고 가정하자.

  • lib1
  • lib2
  • lib3
  • lib4

lib4 :

class ClassD {

    fun tellMeAJoke():String{
        return "You are funny :D"
    }
}

lib3 :

class ClassC {
    fun tellMeAJoke(): String {
        return "You are funny :C"
    }
}

lib2 :

class ClassB {

    val b = ClassD()

    fun whereIsMyJoke(): String {
        return b.tellMeAJoke()
    }
}

lib1 :

class ClassA {

    val c = ClassC()

    fun whereIsMyJoke(): String {
        return c.tellMeAJoke()
    }
}

위 코드를 보면 lib1과 lib2는 각각 lib3과 lib4를 의존합니다. 그러므로 build.gradle 파일에 디펜던시를 추가해야한다.

compile과 api

새로운 api 키워드는 정확히 옛날의 compile 키워드랑 같다.
그러므로 만약 모든 compile가 api로 전부 교체된다고 하더라도 잘 작동될 것이고
이제 lib4와 lib2를 api키워드를 사용해서 APP Module build.gradle 파일에 추가해보자.

lib4 :

dependencies {
	api project(path: ':lib4')
}

lib2 :

dependencies {
	api project(path: ':lib2')
}

이제 lib2와 lib4 모듈에서 다음과 같이 접근이 가능하다.

fun getJokeFromB(): String {
  return ClassB().whereIsMyJoke()
}

fun getJokeFromD(): String {
  return ClassD().tellMeAJoke()
}

그래서 다른점은?

implementation 키워드를 이용해서 lib3과 lib1을 추가해보자.

dependencies {
	implementation project(path: ':lib3')
}
dependencies {
	implementation project(path: ':lib1')
}

두 가지 시나리오가 있는데,

  1. lib4는 api로 컴파일되고 lib4의 구현체가 바뀐다면 gradle은 lib4와 lib2를 모두 recompile하고 lib2를 사용하는 다른 모든 모듈은 lib4를 사용할 수 있다.
  2. 만약 lib3가 변한다고 해도 gradle은 단지 lib3만 recompile한다. 따라서 다른 모듈은 lib3을 바로 사용할 수 없게 된다.

만약 우리가 엄청나게 많은 모듈을 갖고있다면 이 전략은 빌드 속도를 현저히 증가시킬 수 있다.


결론

모든 compile을 전부 implementation으로 바꿔서 빌드를 해서 성공적으로 된다면 아주 잘된것이고 만약 dependency가 부족하면 그때 그러한 라이브러리들을 api 키워드로 바꾸면 된다.


참고 자료

profile
slowly but steadily🐢

0개의 댓글