Ir al contenido

Maven

Por qué se necesita una herramienta de build

Sección titulada «Por qué se necesita una herramienta de build»

En un proyecto de software donde trabaja más de un desarrollador existen: muchos archivos de código fuente, datos de configuración de herramientas y frameworks, y librerías —de terceros y propias— con dependencias entre ellas y entre sus versiones.

Construir un ejecutable es más que compilar: implica empaquetar, probar y —eventualmente— desplegar, con toda la complejidad que trae el servidor de destino, la base de datos, la seguridad. Para que un equipo funcione, ese proceso tiene que ser igual, repetible y automatizado para todos sus integrantes; si no lo es, cada desarrollador puede terminar usando versiones distintas de una librería, empaquetando fuentes distintas, o entregando algo que nunca se probó.

De un build tool esperamos dos servicios:

  • Soporte al ciclo de vida del ejecutable: compilar, empaquetar, probar, desplegar, ejecutar.
  • Manejo de librerías y dependencias.

En nuestros proyectos usamos Maven para el backend en Java/Spring Boot, y npm para el frontend en Angular — cada lenguaje trae su propia herramienta, y ambas cubren los dos servicios anteriores.

Maven nació dentro de Apache: su primer prototipo apareció en 2001, y en 2002 Jason van Zyl reunió esas ideas en la herramienta que se liberó como versión 1.0 en 2004 (ver la historia de Maven). Hoy sigue siendo, junto con Gradle, una de las dos herramientas de build más usadas para proyectos Java.

Maven da soporte a:

  • Administrar las librerías y sus versiones, propias y de terceros.
  • Definir el proceso de construcción y automatizarlo.
  • Manejar las dependencias entre los distintos módulos de un proyecto.
  • Reportar y documentar el proceso y sus resultados.

Además, se integra con otras herramientas para que ese proceso de construcción incluya, por ejemplo, ejecutar pruebas, generar código, desplegar la aplicación, o alimentar tableros de calidad como SonarQube. Por eso Maven es una pieza clave de la Integración Continua en proyectos Java: es lo que la herramienta de CI ejecuta automáticamente en cada push.

En Maven, un módulo es un proyecto — y su tipo determina el artefacto que se construye a partir de sus fuentes: el resultado final de la construcción, listo para desplegarse. Un artefacto es un archivo empaquetado según una estructura estándar; los empaquetados más comunes son:

Empaquetado Para qué
.jar Una librería Java, o una aplicación Spring Boot ejecutable (con su servidor embebido adentro)
.war Una aplicación web pensada para desplegarse en un application server externo
.ear Un paquete que agrupa varios .war/.jar para desplegarlos juntos en un application server JEE

Cada módulo tiene un archivo pom.xml (Project Object Model) que declara su tipo, su identificación, sus dependencias y su configuración de build, en el dialecto XML que define Maven (ver la referencia de pom.xml).

Un artefacto Maven se identifica de forma única con tres datos, su GAV:

  • Group Id: agrupa los proyectos relacionados —típicamente el nombre de la organización o de la aplicación—. Debe ser único a nivel mundial.
  • Artifact Id: el nombre del componente o proyecto puntual.
  • Version: la versión de ese componente.
<project>
<modelVersion>4.0.0</modelVersion>
<groupId>co.edu.uniandes.dsw.books</groupId>
<artifactId>books-api</artifactId>
<version>1.0-SNAPSHOT</version>
</project>

A eso se le agregan el empaquetado y el nombre del proyecto:

<project>
<modelVersion>4.0.0</modelVersion>
<groupId>co.edu.uniandes.dsw.books</groupId>
<artifactId>books-api</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>
<name>books-api</name>
</project>

Un producto puede estar compuesto por varios módulos. En nuestro proyecto Book, por ejemplo, un pom.xml padre puede agrupar el módulo del API y el módulo web:

<project>
<modelVersion>4.0.0</modelVersion>
<groupId>co.edu.uniandes.dsw.books</groupId>
<artifactId>books</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>pom</packaging>
<modules>
<module>books-api</module>
<module>books-web</module>
</modules>
</project>

Cuando corres una acción de Maven sobre el proyecto padre —como mvn package—, esa acción se ejecuta sobre cada uno de sus módulos.

Un módulo hijo puede declarar el padre como <parent>, y así heredar sus dependencias, plugins y configuración sin tener que repetirlos en cada módulo:

<project>
<modelVersion>4.0.0</modelVersion>
<artifactId>books-api</artifactId>
<parent>
<groupId>co.edu.uniandes.dsw.books</groupId>
<artifactId>books</artifactId>
<version>1.0-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
</dependencies>
</project>

Maven ejecuta las tareas de construcción en fases, siempre en el mismo orden. Estas son las principales del ciclo de vida por defecto:

flowchart LR
    A[compile] --> B[test] --> C[package] --> D[verify] --> E[install] --> F[deploy]
Fase Qué hace
compile Compila el código fuente principal
test Corre las pruebas unitarias
package Empaqueta el artefacto (.jar, .war, …)
verify Corre validaciones sobre el paquete, por ejemplo pruebas de integración
install Copia el artefacto al repositorio local, para que otros proyectos en la misma máquina lo puedan usar
deploy Copia el artefacto a un repositorio remoto, para compartirlo con el equipo

Maven guarda los artefactos —los propios y los de terceros— en repositorios, y los identifica por su GAV. Hay tres niveles:

  • Local: en la máquina del desarrollador, en la carpeta .m2. Es lo primero que Maven consulta cuando necesita una dependencia.
  • Central (o global): el repositorio público de Maven, con las librerías de uso compartido más comunes. Se puede explorar en Maven Central.
  • Empresarial: un repositorio propio de la organización —herramientas como Nexus o Artifactory son las más usadas hoy— para librerías internas, o como caché de Maven Central.

Si una dependencia no está en el repositorio local, Maven la busca en los repositorios remotos configurados —el repositorio empresarial, si el proyecto tiene uno, o directamente Maven Central— y la descarga a .m2 para no tener que volver a pedirla.

Una dependencia se declara con su GAV, su tipo de empaquetado, y un scope: cuándo se necesita.

Scope Cuándo se necesita
compile (por defecto) Siempre: en el proyecto y en los proyectos que dependen de él
provided Se espera que el JDK o el servidor ya la provean; no se empaqueta con la aplicación
runtime No hace falta para compilar, pero sí para ejecutar (por ejemplo, un driver JDBC)
test Solo al compilar y correr las pruebas
system Como provided, pero apuntando manualmente a un .jar local en disco
import Importa las dependencias declaradas en el pom.xml de otro proyecto (típico con un BOM, como spring-boot-dependencies)
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
1. ¿Qué identifica de manera única un artefacto en Maven?
2. ¿Por qué un proyecto padre en Maven usa <packaging>pom</packaging>?
3. ¿Dónde busca Maven primero una dependencia que necesita?
4. Una dependencia como el driver JDBC de la base de datos, que no hace falta para compilar pero sí para ejecutar la aplicación, ¿qué scope debería tener?
5. Si ejecutas mvn package y todo corre bien, ¿qué fases ya se ejecutaron antes?
6. ¿Por qué un proyecto Spring Boot típicamente empaqueta como .jar y no como .war?