Construcción de ejecutables y manejo de dependencias: Maven
Un proyecto de software relativamente grande donde trabaja más de un desarrollador tenemos que existen:
- Muchos y diversos archivos de código fuente (decenas, cientos, miles)
- Gran cantidad de datos de configuración de las herramientas, los frameworks, y otros.
- Muchas librerías en diferentes versiones (de terceros, propias, locales) y con dependencias entre ellas.
Construir un ejecutable es un proceso complejo que va más allá de compilar las fuentes. Poner el funcionamiento la aplicación implica realizar un despliegue cuya complejidad dependerá del servidor de aplicaciones, la base de datos, la seguridad, y muchos otros elementos que se deben tener en cuenta.
Para que los equipos de desarrollo puedan funcionar correctamente, estos procesos deben ser iguales, repetibles y automatizados. Afortunadamente, hoy día existen muchas herramientas para apoyar estas labores. Estas herramientas son dependientes del lenguaje de programación en el que está escrito la aplicación. Es común además, tener la aplicación en varios lenguajes, lo que implica que se necesitan varias herramientas para hacer estas tareas. Hay dos grandes servicios que esperamos de estas herramientas:
- Soporte al ciclo de vida de los ejecutables de una aplicación (compilar, empaquetar, probar, desplegar, ejecutar)
- Manejo de librerías y dependencias
La siguiente tabla muestra algunos ejemplos de estas herramientas para diferentes lenguajes:
Lenguaje | Herramienta | Soporte Ciclo de Vida | Manejo de librerías |
---|---|---|---|
Java | Ant | Es una herramienta de automatización de tareas general. El soporte a las tareas de ciclo de vida se puede definir. | |
Maven | OK | OK | |
Scala | Scala-Maven | OK | OK |
Javascript | bower | Ayuda a manejar las dependencias de las librerías javascript | |
grunt | OK |
Maven
Queremos construir un producto de manera repetible: siguiendo el mismo proceso y utilizando las mismas fuentes, versiones de librerías, datos de config, etc. Si el proceso no está bien definido, puede pasar que:
- Se usen las librerías o las versiones de las librerías que no son
- No haya certeza sobre cuáles fuentes se empaquetaron
- No estén resueltas las dependencias entre los componentes
- No se haya probado la aplicación que se empaquetó
- No esté bien configurado el servidor para el despliegue
- Cada desarrollador puede eventualmente hacer un proceso distinto y crear inconsistencias.
Maven es una herramienta creada en 2001 por Jason van Zyl para algunos proyectos de Apache (ver historia de Maven) para administrar la construcción de (ejecutables de) del software.
Las principales actividades para las que Maven da soporte son:
- Administrar las librerías y sus versiones (de terceros y propias)
- Definir el proceso de construcción y automatizarlo
- Manejar las dependencias entre componentes
- Reportar y documentar el proceso y sus resultados
Adicionamente, Maven se integra con otras herramientas de tal forma que el proceso de construcción puede incluir, entre otras cosas:
- Ejecución de pruebas a distintos niveles
- Generación de código
- Despliegue de la aplicación sobre distintos servidores
- Reporte en tableros de control sobre la calidad el código
Maven es una herramienta fundamental para el proceso de Integración Continua en proyectos Java o Scala.
A continuación vamos a explicar las principales características de Maven.
Módulos y artefactos
En el contexto de maven, un módulo
es un proyecto distinto y
el tipo de proyecto determina el artefacto
que se construirá a partir de sus fuentes.
El artefacto es el resultado final de un proceso de construcción y está liso para ser desplegado en el ambiente donde se ejecutará. Un empaquetado es un zip file que, dependiendo de su tipo, empaqueta siguiendo una estructura estándar. Por ejemplo, .war
para proyectos web, .jar
para proyectos java, o .ear
para empaquetados que contienen varios paquetes. Note que las librería en java son archivos .jar
. Un archivo .jar
es un artefacto en maven cuando se conoce de manera precisa:
- Su identificado único o GAV (Group-Artifact-Version)
- El repositorio donde se encuentra y se puede obtener
Cada módulo o proyecto maven tiene un archivo llamado pom.xml
que contiene la información de su tipo, su identificación, sus dependencias y otros elementos para las actividades de construcción del artefacto. Este archivo se escribe utilizando un dialecto xml que está definido en esté enlace: maven pom.xml.
Identificación de artefactos
Un artefacto maven está identificado por su GAV:
- Group Id: Nombre del grupo de proyectos. Relacionados con la compañía o con el nombre de la aplicación. Debe ser único (worldwide).
- Artifact Id: Nombre del componente o aplicación (proyecto:
- Version: Identificador de la versión del componente
<%ace edit=true, lang='java'%> <?xml version="1.0" encoding="UTF-8"?>
<groupId>co.edu.uniandes.csw</groupId>
<artifactId>book_201620</artifactId>
<version>1.0-SNAPSHOT</version>
...
<%endace%>
Otros atributos básicos
- Package Name : Empaquetamiento donde inicialmente será empaquetado el componente (el resultado del
build
) - Name: Nombre del proyecto
<%ace edit=true, lang='xml'%> <?xml version="1.0" encoding="UTF-8"?>
<groupId>co.edu.uniandes.csw</groupId>
<artifactId>book_201620</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>war</packaging>
<name>book_201620</name>
... </project> <%endace%>
Estructura de los proyectos
Estructura Proyectos Maven Java
Proyectos POM y construcción de multi módulos (subsistemas)
Un producto de software puede ser definido como una combinación de varios módulos o subsistemas. En nuestro ejemplo de Book
tenemos que hay un proyecto principal que tiene un pom.xml
que define los dos módulos que componen el proyecto: el web y el back. El siguiente código muestra este archivo:
<%ace edit=true, lang='xml'%>
<groupId>co.edu.uniandes.csw</groupId>
<artifactId>book</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>pom</packaging>
<modules>
<module>book-web</module>
<module>book-back</module>
</modules>
<%endace%>
Cuando se ejecute sobre este proyecto una acción maven, como por ejemplo Clean and Build
, esta acción se ejecutará sobre cada uno de los módulos.
Herencia entre los archivos POM
Tener un archivo POM.xml
padre en el proyecto principal ayuda a factorizar las dependencias, plugins y otras configuraciones y no tener que escribirlas en varios archivos.
<%ace edit=true, lang='xml'%>
Ciclo de Vida
Default lifecycle
- generate-sources/generate-resources
- compile
- test
- package
- integration-test (pre and post)
- Install
- deploy
Manejador de librerías y Dependencias
Depósitos de Dependencias
Los artefactos en Maven se guardan en depósitos de artefactos. Cada artefacto tiene su identificador único o GAV lo que permite hacer búsquedas fácilmente para localizar los artefactos y descargarlos donde se requiera. Maven maneja 3 niveles de depósitos:
- Local: en la máquina del desarrollador (.m2)
- Global: en el sitio de Maven
- Empresarial: en un sitio definido por la organización que desarrolla el proyecto
El depósito local se guarda en la máquina del desarrollador. Cuando se necesita una librería, Maven busca primero allí. La imagen muestra la estructura del depósito:
Los repositorios globales son depósitos públicos que almacenan librerías de uso compartido. Por ejemplo el repositorio mismo de Maven o el repositorio público de Jboss
, Glassfish
o maven central.
El depósito central de Maven se puede ver aquí, tiene una búsqueda con la palabra junit
: central repository
Los repositorios Empresariales tiene la misma estructura que el repositorio local con la diferencia que almacena librerías de uso compartido a nivel de red. En el pom.xml
se debe definir la localización del repositorio:
<%ace edit=true, lang='xml'%>
Hay varias herramientas de repositorios empresariales. Las más populares son Nexus
, Archiva
, Artifactory
.
Cuando se buscan las dependencias (por ejemplo durante la compilación), Maven
primero busca en el repositorio local .m2
, si no las encuentra recurre a sus repositorios empresariales y en caso de no encontrar ahí busca en los repositorios globales.
Definición de dependencias
Una dependencia se describe utilizando:
- su GAV,
- el tipo de empaquetado jar, war, pom, y
- el alcance (cuándo o para qué acciones se requiere la dependencia). El alcance puede ser:
- Compile: scope por defecto. Las dependencias están disponibles en el proyecto y en sus proyectos dependientes.
- Provided: se espera que el JDK, la aplicación o el contenedor provea la dependencia
- Runtime: la dependencia no es requerida en tiempo de compilación pero sí para la ejecución.
- Test: son dependencias que son requeridas solo cuando se compila y ejecuta los test.
- System: similar a provided pero se le debe indicar el jar que contiene la dependencia
- Import: Indica que el POM utilizado debe ser remplazado con las dependencias que éste tenga en su sección
Por ejemplo, en el siguiente fragmento se esta declarando la dependencia a la versión 2.5
de la librería servlet-api.jar
que tiene el groupID javax.servletv.
El alcance de la librería es
provided` (default=compile)
<%ace edit=true, lang='xml'%>