멀티모듈에서 공통 모듈의 의존성이 포함이 안돼요
회사 프로젝트 중에 멀티모듈을 사용하는 레포가 있습니다. gradle 의존성을 정리하던 와중 공통 모듈의 의존성을 포함하지 못하는 이슈가 생겼습니다. 이번에는 해당 이슈를 트러블슈팅한 경험을 소개하겠습니다.
프로젝트 구조
프로젝트 구조는 다음과 같습니다. example-common이라는 이름의 공통 모듈이 하나, example-common을 import 하는 example-service 서비스가 있습니다.
- example-common (공통모듈)
- example-service (서비스)
example-common 모듈의 build.gradle 내용은 다음과 같습니다. 전체는 아니고 일부만 가져왔습니다.
dependencies {
implementation 'org.elasticsearch.client:elasticsearch-rest-client'
}
example-service에서는 example-common모듈의 elasticsearch 라이브러리를 사용하고 싶습니다. 그러나 해당 라이브러리를 사용하니 elasticsearch-rest-client 라이브러리를 참조할 수 없다는 에러 메시지가 뜹니다. 왜 그런 걸까요?
의심 1: 의존성 확인
누가 범인인지 차근차근 생각해 보았습니다. 우선 example-common 모듈의 의존성이 example-service 모듈로 올바르게 전이되고 있는지 확인해야 합니다.
example-service의 build.gradle파일에서 example-common 모듈을 제대로 참조하고 있을까요?
dependencies {
implementation project("example-common")
}
제대로 참조하고 있습니다. 이제 다음 용의자를 만나러 가봅시다.
의심 2: settings.gradle
메인 프로젝트의 settings.gradle 파일에 example-common 모듈이 올바르게 포함되어 있는지 확인해 봅시다.
rootProject.name = 'example'
include 'example-common'
include 'example-service'
역시 제대로 선언되고 있습니다. 도대체 범인은 누구일까요?
해결 완료?
사실 해결방법은 간단합니다. 바로 example-service의 build.gradle 파일에도 똑같은 라이브러리를 추가하면 됩니다.
dependencies {
implementation project("example-common")
implementation 'org.elasticsearch.client:elasticsearch-rest-client'
}
그런데 찝찝하죠? 왜냐면 example-service가 참조하는 example-common에도 똑같은 의존성이 이미 추가가 되어 있기 때문입니다. 너무 비효율적이죠.
example-common 모듈에서 한 번만 의존성을 추가하고, 그 의존성을 example-service에서 사용할 수 있는 방법은 없을까요?
api와 implementation
문제의 핵심은 example-common 모듈의 의존성이 implementation으로 선언되어 있었기 때문입니다. implementation으로 선언하면, 이 모듈의 의존성이 다른 모듈로 전파되지 않습니다.
그러면 모듈의 특정한 의존성을 다른 모듈로 전파하고 싶을 땐 어떤 걸 사용해야 할까요? 바로 api를 사용하면 됩니다.
api와 implementation은 gradle에서 의존성을 선언할 때 사용되는 구성입니다. 이 둘은 모듈 간의 의존성 전파 방식에 영향을 미칩니다.
api
api를 사용하여 의존성을 선언하면, 해당 의존성은 선언된 모듈을 컴파일하는 데 사용될 뿐만 아니라, 해당 모듈을 의존하는 다른 모듈로도 전파됩니다.
예를 들어, A 모듈에서 B 라이브러리를 api로 선언하고, B모듈이 A 모듈에 의존하는 경우, B 모듈은 자동으로 B 라이브러리에 대한 접근 권한을 갖게 됩니다.
implementation
'implementation' 구성을 사용하여 의존성을 선언하면, 해당 의존성은 선언된 모듈 내에서만 사용됩니다. 즉, 해당 모듈을 컴파일할 때만 사용됩니다.
이 구성을 사용하면 컴파일 시간을 단축시키고, 불필요한 의존성이 프로젝트 전반에 걸쳐 전파되는 것을 방지할 수 있습니다.
특정한 목적이 없는 경우에는 implementation 사용을 권장하지만, 이번에는 공통 모듈의 의존성을 가져오고 싶다는 특정한 목적이 있기 때문에 api 구성으로 변경하겠습니다.
해결!
api를 사용하기 위해 example-common 모듈의 build.gradle 파일에 Java 라이브러리 플러그인을 적용합시다. 저는 이 과정을 빠트려서 한번 더 머리를 싸맸습니다.
// example-common의 build.gradle 파일 상단에 추가
plugins {
id 'java-library'
}
플러그인 적용 후에 example-common 모듈의 build.gradle 파일에서 api 의존성을 사용하여 elasticsearch 라이브러리를 선언합니다.
plugins {
id 'java-library'
}
dependencies {
api 'org.elasticsearch.client:elasticsearch-rest-client'
}
gradle 파일을 이렇게 변경하고, 다시 빌드합니다.
이렇게 하면 example-service 모듈에서 example-common 모듈이 올바르게 인식됩니다.