Gradle에서 외부 라이브러리를 프로젝트에 추가할 때 compileOnly, runtimeOnly, implementation, api 등 다양한 키워드를 사용할 수 있습니다. 이 중에서도 api와 implementation은 가장 자주 사용되는 옵션으로, 각각의 기능과 동작 방식이 다르기 때문에 상황에 맞게 사용해야 프로젝트의 의존성을 효율적으로 관리할 수 있습니다. 이 글에서는 api와 implementation의 차이와 사용법을 자세히 알아보겠습니다.
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되었고, 대신 api와 implementation으로 기능을 나누어 사용하게 되었습니다.
api와 implementation의 차이
1. 의존성 전이 여부
- api: api로 설정된 의존성은 해당 모듈을 사용하는 다른 모듈에도 노출됩니다. 즉, A 모듈에서 api로 의존성을 추가했다면 A 모듈을 의존하는 B 모듈에서도 해당 라이브러리를 사용할 수 있습니다. 이를 의존성이 전이(transitive)된다고 표현합니다.
- implementation: 반면, implementation으로 설정된 의존성은 해당 모듈 내부에서만 사용되며 다른 모듈로 전이되지 않습니다. 이를 통해 의존성을 캡슐화하여 내부 구현을 외부에 숨길 수 있으며, 모듈 간의 불필요한 의존성 노출을 방지할 수 있습니다.
2. 사용 예시와 설명
아래는 의존성 전이와 관련하여 몇 가지 시나리오를 예시로 설명합니다.
예시 1: Module B가 Module A를 api로, Module C가 Module B를 api로 의존
- 설명: Module B는 Module A를 api로 의존하고 있습니다. 그렇기 때문에 Module C도 Module 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 B가 Module A를 api로, Module C가 Module B를 implementation으로 의존
- 설명: Module B는 Module A의 의존성을 api로 선언했기 때문에 Module C는 Module 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 B가 Module A를 implementation으로, Module C가 Module B를 api로 의존
- 설명: Module B는 Module A를 implementation으로 의존하고 있기 때문에 Module A의 의존성은 Module B 내부에서만 사용됩니다. 따라서 Module C는 Module 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 B가 Module A를 implementation으로, Module C가 Module B를 implementation으로 의존
- 설명: Module B와 Module C 모두 implementation으로 설정된 경우, Module A의 의존성은 전이되지 않습니다. 따라서 Module C는 Module A의 클래스에 접근할 수 없습니다.
// Module B의 build.gradle
dependencies {
implementation project(':ModuleA')
}
// Module C의 build.gradle
dependencies {
implementation project(':ModuleB')
}
// Module C에서 사용
Hello.sayHello(); // 오류 발생: Module A는 전이되지 않음
api와 implementation의 사용 지침
Gradle에서는 가능한 한 **implementation**을 사용하는 것을 권장합니다. 이유는 다음과 같습니다:
- 캡슐화: implementation을 사용함으로써 모듈 간의 의존성을 캡슐화하고, 내부 구현을 외부로부터 숨길 수 있습니다. 이는 모듈의 내부 동작을 보호하고 변경에 유연성을 제공합니다.
- 컴파일 시간 단축: implementation으로 설정된 의존성은 전이되지 않기 때문에, 라이브러리를 제공하는 모듈에서 의존성을 변경해도 다른 모듈은 재컴파일이 필요하지 않습니다. 이는 컴파일 시간을 단축하고 재빌드 빈도를 줄일 수 있는 장점이 있습니다.
특히, 모듈형 모놀리식 구조에서는 이러한 캡슐화가 더욱 중요합니다. 각 모듈의 독립성을 유지하면서 전체 애플리케이션을 구성하는 구조에서는 implementation을 사용하여 모듈 간의 불필요한 의존성 전이를 막고 재컴파일 시간을 줄일 수 있습니다.
결론
api와 implementation의 차이를 이해하고, 상황에 맞게 사용하는 것이 Gradle 프로젝트에서 의존성을 효과적으로 관리하는 핵심입니다. 무분별하게 api를 사용하면 의존성이 전이되어 다른 모듈에 영향을 미칠 수 있으므로, 일반적으로는 implementation을 사용하여 의존성을 캡슐화하는 것이 좋습니다. 다만, 해당 모듈의 외부에서도 의존성을 필요로 할 때에는 api를 사용하는 것이 적절합니다.
'개발일지 > 스프링' 카테고리의 다른 글
스프링 부트에서 최초 실행 시 동작하는 로직 (0) | 2024.10.31 |
---|---|
Faker 라이브러리 사용하는 방법 (1) | 2024.10.31 |
자바 스프링부트 웹 개발에서 중요한 디자인 패턴과 활용 (0) | 2024.10.29 |
MyBatis 사용법 [gradle 사용] (0) | 2024.03.27 |
서블릿 Ver.1 (FrontController) (0) | 2024.03.07 |
댓글