Christmas Pikachu Gradle 의존성 설정의 이해 - api와 implementation의 차이
개발일지/스프링

Gradle 의존성 설정의 이해 - api와 implementation의 차이

ZI_CO 2024. 10. 29.

Gradle에서 외부 라이브러리를 프로젝트에 추가할 때 compileOnly, runtimeOnly, implementation, api 등 다양한 키워드를 사용할 수 있습니다. 이 중에서도 apiimplementation은 가장 자주 사용되는 옵션으로, 각각의 기능과 동작 방식이 다르기 때문에 상황에 맞게 사용해야 프로젝트의 의존성을 효율적으로 관리할 수 있습니다. 이 글에서는 apiimplementation의 차이와 사용법을 자세히 알아보겠습니다.

Gradle 의존성 설정 기본 예시

// build.gradle 파일

dependencies {
    api 'org.apache.httpcomponents:httpclient:4.5.7'
    implementation 'org.apache.commons:commons-lang3:3.5'
}

이 예시에서 httpclient 라이브러리는 api로 설정되었고, commons-lang3 라이브러리는 implementation으로 설정되었습니다.

참고로 Gradle 6.x 이전에는 compile이라는 키워드를 사용하여 라이브러리를 추가했습니다. 하지만 이후 버전에서는 compile이 deprecated되었고, 대신 apiimplementation으로 기능을 나누어 사용하게 되었습니다.

apiimplementation의 차이

1. 의존성 전이 여부

  • api: api로 설정된 의존성은 해당 모듈을 사용하는 다른 모듈에도 노출됩니다. 즉, A 모듈에서 api로 의존성을 추가했다면 A 모듈을 의존하는 B 모듈에서도 해당 라이브러리를 사용할 수 있습니다. 이를 의존성이 전이(transitive)된다고 표현합니다.
  • implementation: 반면, implementation으로 설정된 의존성은 해당 모듈 내부에서만 사용되며 다른 모듈로 전이되지 않습니다. 이를 통해 의존성을 캡슐화하여 내부 구현을 외부에 숨길 수 있으며, 모듈 간의 불필요한 의존성 노출을 방지할 수 있습니다.

2. 사용 예시와 설명

아래는 의존성 전이와 관련하여 몇 가지 시나리오를 예시로 설명합니다.

예시 1: Module BModule Aapi로, Module CModule Bapi로 의존

  • 설명: Module BModule Aapi로 의존하고 있습니다. 그렇기 때문에 Module CModule A의 클래스(예: Hello)를 사용할 수 있습니다. 즉, Module A의 의존성이 전이되어 모든 모듈에서 접근이 가능합니다.
// Module A의 build.gradle
public class Hello {
    public static void sayHello() {
        System.out.println("Hello from Module A");
    }
}

// Module B의 build.gradle
dependencies {
    api project(':ModuleA')
}

// Module C의 build.gradle
dependencies {
    api project(':ModuleB')
}

// Module C에서 사용
Hello.sayHello(); // 사용 가능

예시 2: Module BModule Aapi로, Module CModule Bimplementation으로 의존

  • 설명: Module BModule A의 의존성을 api로 선언했기 때문에 Module CModule B의 내부 구현에 접근할 수 없지만 Module A의 의존성은 사용할 수 있습니다. 이는 implementation으로 의존하더라도 api로 연결된 의존성은 전이된다는 것을 의미합니다.
// Module B의 build.gradle
dependencies {
    api project(':ModuleA')
}

// Module C의 build.gradle
dependencies {
    implementation project(':ModuleB')
}

// Module C에서 사용
Hello.sayHello(); // 사용 가능

예시 3: Module BModule Aimplementation으로, Module CModule Bapi로 의존

  • 설명: Module BModule Aimplementation으로 의존하고 있기 때문에 Module A의 의존성은 Module B 내부에서만 사용됩니다. 따라서 Module CModule A의 클래스에 접근할 수 없습니다.
// Module B의 build.gradle
dependencies {
    implementation project(':ModuleA')
}

// Module C의 build.gradle
dependencies {
    api project(':ModuleB')
}

// Module C에서 사용
Hello.sayHello(); // 오류 발생: Module A는 노출되지 않음

예시 4: Module BModule Aimplementation으로, Module CModule Bimplementation으로 의존

  • 설명: Module BModule C 모두 implementation으로 설정된 경우, Module A의 의존성은 전이되지 않습니다. 따라서 Module CModule A의 클래스에 접근할 수 없습니다.
// Module B의 build.gradle
dependencies {
    implementation project(':ModuleA')
}

// Module C의 build.gradle
dependencies {
    implementation project(':ModuleB')
}

// Module C에서 사용
Hello.sayHello(); // 오류 발생: Module A는 전이되지 않음

apiimplementation의 사용 지침

Gradle에서는 가능한 한 **implementation**을 사용하는 것을 권장합니다. 이유는 다음과 같습니다:

  • 캡슐화: implementation을 사용함으로써 모듈 간의 의존성을 캡슐화하고, 내부 구현을 외부로부터 숨길 수 있습니다. 이는 모듈의 내부 동작을 보호하고 변경에 유연성을 제공합니다.
  • 컴파일 시간 단축: implementation으로 설정된 의존성은 전이되지 않기 때문에, 라이브러리를 제공하는 모듈에서 의존성을 변경해도 다른 모듈은 재컴파일이 필요하지 않습니다. 이는 컴파일 시간을 단축하고 재빌드 빈도를 줄일 수 있는 장점이 있습니다.

특히, 모듈형 모놀리식 구조에서는 이러한 캡슐화가 더욱 중요합니다. 각 모듈의 독립성을 유지하면서 전체 애플리케이션을 구성하는 구조에서는 implementation을 사용하여 모듈 간의 불필요한 의존성 전이를 막고 재컴파일 시간을 줄일 수 있습니다.

결론

apiimplementation의 차이를 이해하고, 상황에 맞게 사용하는 것이 Gradle 프로젝트에서 의존성을 효과적으로 관리하는 핵심입니다. 무분별하게 api를 사용하면 의존성이 전이되어 다른 모듈에 영향을 미칠 수 있으므로, 일반적으로는 implementation을 사용하여 의존성을 캡슐화하는 것이 좋습니다. 다만, 해당 모듈의 외부에서도 의존성을 필요로 할 때에는 api를 사용하는 것이 적절합니다.

댓글