В этой статье рассмотрим конфигурации Gradle: api и implementation.
Напишем три библиотеки. Первая — animal-api. А другие две (api-animalclient и impl-animalclient) абсолютно одинаковы и содержат animal-api как зависимость, но посредством разных конфигураций: api и implemenation.
В главном приложении будем использовать два клиента по очереди.
Разница
- api (раньше compile): Зависимость animal-api объявляется как часть публичного API библиотеки animalclient. Это означает, что она будет транзитивно доступна потребителю библиотеки animalclient (главному приложению).
api("ru.sysout:animal-api:0.0.1-SNAPSHOT")
- implementation: Зависимость используется только внутри библиотеки animalclient. Она не попадает в classpath компиляции потребителя библиотеки animalclient, скрывая внутреннюю реализацию.
implementation("ru.sysout:animal-api:0.0.1-SNAPSHOT")
Проверим это.
Библиотека ru.sysout:animal-api
Содержит модель данных, которую мы хотим использовать в других местах, AnimalsDto:
data class AnimalsDto(val description: String, val animals: List<AnimalDto>) data class AnimalDto(val id: String, val name: String)
Библиотеки ru.sysout:api-animalclient и ru.sysout:impl-animalclient
Они абсолютно одинаковые, кроме способа включения зависимости animal-api в файлах build.gradle.kts.
api-animalclient подключает зависимость animal-api с помощью api:
api("ru.sysout:animal-api:0.0.1-SNAPSHOT")
а impl-animalclient с помощью implementation:
implementation("ru.sysout:animal-api:0.0.1-SNAPSHOT")
Обе библиотеки-клиенты содержат простой код для получения AnimalsDto (через REST).
Использование
Теперь создадим главное приложение на Kotlin/Gradle и добавим в него одну из наших библиотек-клиентов.
build.gradle.kts (главного приложения):
// Вариант 1: используем клиент, который добавил зависимость через API
implementation("ru.sysout:api-animalclient:0.0.1-SNAPSHOT")
// Вариант 2: используем клиент, который добавил зависимость через Implementation
//implementation("ru.sysout:impl-animalclient:0.0.1-SNAPSHOT")
Попробуем использовать клиент и модель данных в сервисе AnimalService:
import org.springframework.stereotype.Service
import ru.sysout.spring_multimodule.animalapi.AnimalsDto
import ru.sysout.spring_multimodule.api.animalclient.AnimalClient
//import ru.sysout.spring_multimodule.impl.animalclient.AnimalClient либо этот
@Service
class AnimalService(val animalClient: AnimalClient) {
fun getAnimals(): AnimalsDto?{
return animalClient.getAnimals()
}
}
В чем разница?
Здесь и проявляется ключевое различие:
- С api-animalclient: Код скомпилируется успешно. Так как зависимость animal-api была объявлена через api, она транзитивно «протекает» в главное приложение. Главное приложение «видит» и может использовать класс AnimalsDto.
- С impl-animalclient: Компилятор выдаст ошибку на строке импорта AnimalsDto. Он просто не найдет этот класс. Зависимость animal-api была объявлена через implementation и надежно скрыта внутри библиотеки impl-animalclient. Главное приложение ее не видит.
Как это исправить?
Чтобы код с impl-animalclient скомпилировался, нам пришлось бы вручную и явно добавить зависимость animal-api уже в главное приложение:
dependencies {
implementation("ru.sysout:impl-animalclient:0.0.1-SNAPSHOT")
// Вынуждены явно дополнительно добавить зависимость
implementation("ru.sysout:animal-api:0.0.1-SNAPSHOT")
}
Итоги
Мы рассмотрели разницу между implementation и api в Gradle. Рекомендуется начинать с implementation. Переходить на api нужно только тогда, когда вы уверены, что классы из этой зависимости должны быть частью публичного контракта вашей библиотеки и использоваться теми, кто ее подключает. Это правило делает сборки быстрее и предотвращает «загрязнение» classpath потребителя ненужными зависимостями.
Код трех библиотек доступен на Github (главное приложение там отсутствует, но оно может быть любым).