[Gradle] dependencies 설정 알아보기(feat. api와 implementation 구분)
배경 및 목표
Java나 Kotlin 프로젝트를 진행하다보면, 여러 모듈 또는 라이브러리 의존을 하도록 build.gradle(또는 build.gradle.kts)에 의존성을 추가합니다. 의존성을 추가할 때에 사용할 수 있는 타입에는 여러개가 있고 어떤 상황에서 어떤 타입을 사용해야 하는지 정리하고자 포스팅을 하게 되었습니다.
Gradle
Gradle의 기본 구조
아래 코드는 build.gradle.kts 예시입니다. 멀티모듈 구조를 사용하는 프로젝트의 루트 gradle 파일이라, 단일 모듈 프로젝트의 gradle 파일과 조금 상이할 수 있습니다.
- build.gradle 파일은 보통 plugins, repositories, dependencies 블록으로 구성됩니다.
/**
* 참고)
* - kotlin with springboot tutorials: https://spring.io/guides/tutorials/spring-boot-kotlin
*/
plugins {
id "org.springframework.boot" version "3.2.2"
id "io.spring.dependency-management" version "1.1.4"
}
allprojects {
apply plugin: 'org.springframework.boot'
apply plugin: 'io.spring.dependency-management'
apply plugin: 'java'
group = 'h'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '17'
configurations {
compileOnly {
extendsFrom annotationProcessor
}
}
repositories {
mavenCentral()
}
dependencies {
runtimeOnly 'com.h2database:h2'
implementation('org.springframework.boot:spring-boot-starter-web')
testImplementation 'org.springframework.boot:spring-boot-starter-test'
}
test {
useJUnitPlatform()
}
}
project(':spring-demo') {
dependencies {
compileOnly 'org.projectlombok:lombok'
annotationProcessor 'org.projectlombok:lombok'
implementation('org.springframework:spring-jdbc')
implementation('com.zaxxer:HikariCP:4.0.3')
}
tasks.register('prepareKotlinBuildScriptModel') {}
}
project(':kotlin-demo') {
dependencies {
implementation 'com.fasterxml.jackson.module:jackson-module-kotlin'
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core'
testImplementation 'io.kotest:kotest-runner-junit5:5.6.2'
testImplementation 'io.kotest:kotest-property:5.6.2'
}
}
Dependency 설정
gradle의 dependencies 설정은 크게 4가지 파트로 구성되어 있으며, 보통은 group, name 또는 group, name, version까지 작성합니다.
- group: 의존하고자 하는 모듈/라이브러리의 조직, 회사 또는 프로젝트명
- name: 의존하고자 모듈/라이브러리명
- version: 의존하고자 하는 버전
- classifier: group, name, version이 동일할 때 추가적으로 의존성을 구분해야 할 때 사용
dependencies 설정할 때 사용할 수 있는 타입은 아래와 같습니다.
- api: 의존을 명시적으로 설정하여 classpath에 노출할 때 사용하며, 외부에 노출됩니다.
- implementation: 해당 모듈 내부에서만 의존할 때 사용합니다.
- compileOnly: 컴파일 시에만 필요할 때 사용하며, 런타임 classpath나 테스트 classpath에는 포함되지 않습니다.
- compileOnlyApi: 컴파일 시에만 사용되며, 외부에도 노출합니다.
- runtimeOnly: 런타임에만 필요하고, 컴파일 시에는 사용이 불가한 종속성에 대해 설정합니다.
- testImplementation: 테스트를 컴파일할 때 필요한 의존성에 설정합니다.
- testCompileOnly: 테스트 컴파일 시점에만 필요할 경우 설정합니다.
- testRuntimeOnly: 테스트 런타임에만 필요할 경우 설정
dependencies에서 api를 사용할 수 없는 경우 - Java Library 플러그인 설치 필요
gradle 7로 올라감에 따라 compile 타입이 deprecated되고 api가 새로 등장했습니다. 만약 api()를 찾지 못한다는 아래와 같은 에러 메시지가 발생한다면, 추가적으로 플러그인 설치가 필요합니다.
Could not find method api() for arguments ...
설치 필요한 플러그인은 Java Library 플러그인입니다. build.gradle.kts에 아래와 같이 java-library를 플러그인에 추가해주면 되며, Java Library 플러그인은 Java 라이브러리에 대해 특정 지식을 제공해 Java 플러그인을 확장한 것이라고 보면 됩니다.
plugins {
`java-library`
}
참고로 Java Library 플러그인을 통해 설정되는 dependencies는 아래와 같이 세팅됩니다.
- 초록색 사각형: 의존성 선언 시 사용하는 구성
- 핑크색 사각형: 구성 요소가 컴파일되거나 라이브러리에 대해 실행될 때 사용되는 구성
- 파란색 사각형: 구성 요소 내부에서 자체적으로 사용하기 위한 구성
api와 implementation의 차이점
api보다는 implementation을 사용하는 것을 권장합니다. 만약, api 타입을 사용해야 한다면 정말 필요한 것인지 한 번 더 확인하고 사용하는 것이 좋습니다.
api와 implementation 타입 모두 실 서비스 로직에서 의존성을 추가하는 타입으로, 이 둘 간에는 의존성이 적용되는 범위에 차이가 있습니다.
- api 적용 범위
- 모듈에서 api 타입을 통해 의존성 설정을 할 경우, 해당 라이브러리는 모듈을 의존하는 또 다른 모듈에서도 접근이 가능해집니다.
- api를 통해 사용할 경우, 의존성 설정된 내용이 불필요하게 중복적으로 추가될 수 있습니다.
- implementation 적용 범위
- 모듈에서 implementation 타입을 통해 의존성 설정을 하면, 해당 모듈을 의존하는 다른 모듈에서는 해당 의존성에 접근이 불가능합니다.
- 특정 모듈에 격리하고자 하는 의존성이 있을 때 implementation을 사용하며, 기본적으로는 api 대신 implementation을 쓰는 것이 좋습니다.
dependencies 설정에 설명 추가하기
의존성 설정한 내용에 대해 제약 조건 등 작성하고 싶은 경우, 아래와 같이 의존성 설정 시 명시할 수 있습니다.
dependencies {
implementation("org.ow2.asm:asm:7.1") {
because("we require a JDK 9 compatible bytecode generator")
}
}
참고 자료