Declarando dependencias: implementation, api, compileOnly, runtimeOnly y testImplementation
Elige la configuracion correcta para que tu build sea rapido y tu API quede limpia.
Cada linea dentro de un bloque `dependencies {}` de Gradle asocia una libreria a una *configuracion* con nombre. Una configuracion es simplemente un contenedor de dependencias con reglas sobre *cuando* son visibles: en tiempo de compilacion, en tiempo de ejecucion, solo durante las pruebas y, sobre todo, si se *filtran* a los proyectos que dependen del tuyo. Los plugins de Kotlin y Java traen un conjunto estandar de estos contenedores, y elegir el correcto es la mayor palanca que tienes sobre la velocidad del build y la limpieza de los limites entre modulos. La eleccion equivocada igual compila, asi que los errores se esconden con facilidad hasta que un refactor o un consumidor los saca a la luz.
`implementation` es tu opcion por defecto. La dependencia esta en el classpath de compilacion y de ejecucion de *este* modulo, pero es un detalle interno: no aparece en el classpath de *compilacion* de los consumidores. Es decir, si el modulo `:app` depende de `:lib`, y `:lib` usa Gson con `implementation`, entonces `:app` no puede hacer `import com.google.gson.*` por accidente. La ganancia es doble: una superficie publica mas limpia y builds incrementales mucho mas rapidos, porque cambiar una dependencia `implementation` solo recompila `:lib`, no todo lo que esta aguas abajo. Usa `implementation` salvo que tengas una razon concreta para no hacerlo.
`api` es la excepcion deliberada. Pone la dependencia tambien en el classpath de compilacion del consumidor, asi que se *filtra* a proposito. Usala solo cuando un tipo de esa libreria aparece en la API publica de tu modulo, por ejemplo una funcion publica que devuelve un `OkHttpClient` o que recibe un `ImmutableList` de Guava como parametro. Si un consumidor debe poder nombrar el tipo para llamar a tu codigo, la dependencia forma parte de tu contrato y debe ser `api`. Abusar de `api` es un error clasico: reconstruye todo el grafo en cada cambio y acopla silenciosamente a los consumidores con librerias que nunca pidieron. Ten en cuenta que la configuracion `api` requiere el plugin `java-library` (o el plugin JVM de Kotlin, que lo provee).
`compileOnly` y `runtimeOnly` son las dos mitades de la division del classpath. `compileOnly` provee una libreria en tiempo de compilacion pero nunca la empaqueta para ejecucion, util para artefactos solo de anotaciones, APIs de servlet provistas por el contenedor o librerias que esperas que el entorno aporte. `runtimeOnly` es la imagen espejo: ausente al compilar, presente al ejecutar, ideal para cosas que solo referencias por configuracion o reflexion como un driver JDBC, un backend de logging de SLF4J como Logback o un modulo de Jackson solo de runtime. Ninguna se filtra a los consumidores. Para procesadores de anotaciones, prefiere las configuraciones dedicadas `kapt`, `ksp` o `annotationProcessor` en lugar de `compileOnly`.
Las configuraciones de pruebas forman una jerarquia paralela. `testImplementation` agrega una libreria solo a los classpaths de compilacion y ejecucion de las pruebas, nunca al codigo de produccion ni a los consumidores, que es justo donde van JUnit, MockK, Kotest o Truth. Sus hermanas `testCompileOnly` y `testRuntimeOnly` (por ejemplo el launcher de la plataforma JUnit) siguen las mismas reglas que sus equivalentes principales. Como las dependencias de prueba viven en sus propios contenedores, no agregan peso al artefacto que distribuyes ni aparecen en el classpath de otro modulo.
Dos habitos practicos mantienen todo esto manejable. Primero, centraliza las versiones en un *catalogo de versiones* de Gradle (`gradle/libs.versions.toml`) para que cada modulo referencie las mismas coordenadas mediante accesores `libs.*` con tipado seguro en vez de cadenas escritas a mano. Segundo, ante la duda usa `implementation` por defecto y amplia a `api` solo cuando el compilador o un consumidor realmente te obliguen. La prueba mental es simple: pregunta "un proyecto que depende de mi necesita *ver* este tipo para usar mi codigo?" Si la respuesta es si, es `api`; si es no, dejalo en `implementation` y disfruta del build mas rapido y aislado.
// gradle/libs.versions.toml — centralized version catalog[versions]kotlin = "2.1.0"okhttp = "4.12.0"junit = "5.11.3"[libraries]okhttp = { module = "com.squareup.okhttp3:okhttp", version.ref = "okhttp" }logback = { module = "ch.qos.logback:logback-classic", version = "1.5.12" }postgres = { module = "org.postgresql:postgresql", version = "42.7.4" }junit-jupiter = { module = "org.junit.jupiter:junit-jupiter", version.ref = "junit" }junit-launcher = { module = "org.junit.platform:junit-platform-launcher", version = "1.11.3" }[plugins]kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" }
Define versions and coordinates once; modules reference them via type-safe libs.* accessors.
// build.gradle.kts — a realistic dependencies block for a library moduleplugins {`java-library` // enables the leaking `api` configurationalias(libs.plugins.kotlin.jvm)}dependencies {// Leaks to consumers: OkHttp types appear in this module's public APIapi(libs.okhttp)// Internal detail: present here, hidden from consumers, faster rebuildsimplementation("com.google.code.gson:gson:2.11.0")// Compile-only: annotations not needed at runtimecompileOnly("org.jetbrains:annotations:24.1.0")// Runtime-only: never imported, loaded by reflection/config at run timeruntimeOnly(libs.postgres)runtimeOnly(libs.logback)// Test-only: invisible to production code and to consumerstestImplementation(libs.junit.jupiter)testRuntimeOnly(libs.junit.launcher)}tasks.test { useJUnitPlatform() }
api leaks on purpose; implementation/compileOnly/runtimeOnly/test* stay private to this module.
🧠Comprueba tu comprensión
0/1 · 0/1 answered1. Module :lib has a public function `fun client(): OkHttpClient`, and module :app depends on :lib. Which configuration must :lib use for OkHttp so :app can name the OkHttpClient return type?