viernes, 25 de mayo de 2012

Entendiendo  Apache Maven

Podríamos decir que Maven es una herramienta que sirve para gestionar nuestro proyecto software de tecnología JAVA en distintas fases de éste. Maven nos permite desde compilar un proyecto hasta generar una serie de acciones sobre el mismo.

Maven nacio con el proposito de simplificar el proceso del build de un projecto software. 


En nuestros proyectos java siempre tenemos varias tareas que realizar. La primera suele ser crear una estructura de directorios para nuestro proyecto, con un hueco para los fuentes, otro para iconos, ficheros de configuración o datos, directorio para dejar los .class o el .jar, para dejar el javadoc, etc.

Después, tenemos más tareas que realizamos con cierta frecuencia, como borrar los .class, compilar, generar la documentción de javadoc, el jar, incluso generar documentación web para publicar nuestro trabajo. Posiblemente acabemos haciendo algunos scripts o ficheros .bat para todas estas tareas.

Si nuestro programa es grande, incluso es posible que dependamos de otros jar externos, como drivers de base de datos, JUnit para clases de test, log4j para nuestra salida de log, etc.  Tendremos que copiar todos esto jar externos en algún sitio de nuestro proyecto e incluirlos.

Una primera herramienta que nos ayuda un poco con todo esto es ant. Sin embargo, con ant no tenemos todas estas tareas hechas y debemos rescribir posiblemente nuestro fichero build.xml (el de tareas que se ejecutan con ant) de un proyecto a otro. También tendremos que copiar los jar externos de los que dependemos.

Una herramienta que nos solucionara muchas complicaciones es maven, con comandos simples, nos crea una estructura de directorios para nuestro proyecto con sitio para los fuentes, los iconos, ficheros de configuración y datos, etc.

Si a maven le indicamos qué jar externos necesitamos, es capaz de ir a buscarlos a internet y descargarlos por nosotros, Sin necesidad prácticamente de configurar nada, maven sabe como borrar los .class, compilar, generar el jar, generar el javadoc y generar un documentación web con montones de informes (métricas, código duplicado, etc). 

Maven se encarga de pasar automáticamente nuestros test de prueba cuando compilamos. Incluso maven nos genera un zip de distribución en el que van todos los jar necesarios y ficheros de configuración de nuestro proyecto.


Manejo de dependencias y Repositorios

Una de las necesidades con las que nos encontramos ya sea al empezar un nuevo proyecto o durante el desarrollo del mismo, es la de tener que incorporar dependencias (librerías externas a nuestra aplicación y frameworks, entre otros).

Si bien la forma de manejar estas dependencias no era una tarea complicada, Maven nos brinda aun mayores facilidades para incorporarlas y organizarlas. Veremos que en lugar de tener replicadas las librerías en cada uno de nuestros proyectos pasaremos a tener un “lugar” único donde estarán almacenadas, haciendo así referencia a la ubicación donde se encuentran en lugar de tener una copia física en cada proyecto.

Uno de los beneficios de lo previamente mencionado es que no tendremos nuestras dependencias almacenadas en el mismo repositorio donde tenemos nuestro código (svn, cvs, perforce, etc) sino que las utilizaremos desde losrepositorios de Maven.

Veamos un ejemplo simple que nos ayudará a entender un poco más el manejo de dependencias que nos brinda Maven, pero solo en forma conceptual.

Al momento de necesitar de un framework nuevo, por ejemplo Hibernate, antes era necesario analizar qué dependencias tenía Hibernate, bajarlas e incluirlas manualmente en el proyecto. Era necesario analizar todas las librerías necesarias e incorporarlas a nuestro proyecto en forma manual. Maven en este caso nos brinda una gran ayuda simplificando ese proceso en solo dos pasos, elegir el framework a incorporar, ejemplo Hibernate, y luego declarar en la configuración de Maven la dependenciaMaven hará el resto. Se encargará de incorporar a nuestro proyecto todo lo necesario para que Hibernate funcione correctamente.

Para ir entendiendo de a poco cómo lograremos ésto, cabe mencionar que maven tiene un archivo de configuración que será relativo al proyecto en el que trabajemos, también mantiene una nomenclatura sobre cómo nombrar y versionar las dependencias, es así que partiendo de ciertos valores defaults resulta tan sencillo agregar una dependencia a nuestro proyecto.

Para poder soportar el esquema previamente mencionado Maven incorpora el concepto de repositorio de dependencias. El concepto principal es el de tener un único lugar donde ubicar las librerías, es así que encontraremos repositorios subidos a la Web que tendrán aquellas dependencias que se usan cotidianamente. De todos modos, Maven también creará un repositorio local en cada maquina donde se lo utilice con el fin de no bajar nuevamente una dependencia cada vez que la necesitemos. Este repositorio local es el “lugar” del que hablábamos al comienzo de este punto (una simple carpeta que será utilizada por Maven para guardar las librerías).

Es importante señalar que se pueden crear nuestros propios repositorios y configurar nuestros proyectos para que se comuniquen con ellos por medio de la Web, en los cuales podríamos incorporar todas las librerías que sabemos serán utilizadas.



Conociendo a los Obreros del Proceso

Si bien se define a Maven como una herramienta muy fuerte y completa, si entramos en detalle Maven no sabe hacer mucho mas que entender XMLs y delegar responsabilidades.

Maven se abstrajo de todas las tareas especificas, como ser build, compilaciones, y tantas otras tareas que son sustentables por si mismas, generando plugins para cada una de ellas.

El núcleo central de Maven se conforma de una serie de plugins que saben dedicarse a hacer tareas específicas, y que se mantienen centralizadamente, pero, y esta es la parte importante, se comparten universalmente en los mismos repositorios donde encontrábamos las dependencias.

Esta organización por medio de plugins genera en primer lugar que las distintas distribuciones de Maven sean livianas en tiempo de descarga, ya que el funcionamiento en si mismo se encuentra en los plugins en los repositorios y sólo se descargaran al ejecutar Maven por primera vez.

Pero la consecuencia mas importante es que nos permite reutilizar plugins creados por otros y mantenidos por otros con solo declararlos en el archivo de configuración de Maven. Y si una nueva funcionalidad aparece, con solo actualizar la versión en este archivo de configuración, obtendremos las actualizaciones disponibles.

Resumiendo este tema en un caso concreto, cada vez que le digamos a Maven que efectúe alguna tarea como ser que “ejecute los tests” o que “empaquete la aplicación” o muchas otras acciones que podremos pedirle, lo único que Maven hará será buscar el plugin que sabe cómo realizar la tarea especificada y decirle que la ejecute. Todo lo demás es responsabilidad de los diferentes plugins.

Ciclos de vida, Fases y Goals

Cuando logramos entender que Maven es un orquestador que utiliza entes que son los que saben realizar las tareas solicitadas, la pregunta seria como alcanza los diferentes objetivos.


Maven basa su funcionamiento en algo llamado ciclo de vida (Life cycle), un ciclo de vida es una secuencia organizada de fases (maven phases), a su vez una fase puede tener asociado uno o mas goals.


El concepto principal de contar con un Life cycle es el de tener una estructura común de pasos a seguir. 
Imaginemos éstos conceptos previamente mencionados (Life cycle, phases, goals) como una lista de instrucciones que están disponibles para ser ejecutadas por el usuario.
 Esta lista de instrucciones es independiente al proyecto. Si bien las instrucciones son las mismas para todos los proyectos, éstas podrían configurarse para hacer cosas específicas.


Pasemos a un ejemplo real y concreto de un ciclo de vida en Maven. Es importante mencionar que Maven tiene varios ciclos de vida pero para este primer ejemplo veremos el más sencillo de todos, ciclo de vida “Clean”.


Como dijimos un ciclo de vida tiene fases. En el caso del ciclo de vida “Clean” las fases que contiene son:
  1. pre-clean
  2. clean
  3. post-clean
Imaginemos que cada una de las fases nombradas tiene asociada una lógica específica, y nosotros queremos ejecutar la lógica asociada a la fase “pre-clean”. Para ésto bastará con ejecutar el comando “mvn pre-clean”, fíjense que lo único que hacemos es decirle a Maven que ejecute una fase específica.


Ahora bien, con antelación dijimos que las fases tenían un orden especifico, ¿cómo influye ésto al momento de ejecutar una fase en Maven?, sencillo, en el ejemplo donde le dijimos a Maven que ejecute “pre-clean” Maven se fijará en su lista de fases para el life-cycle asociado (la fase pre-clean está asociada al life-cycle “Clean”) qué fases tiene previas a la que nosotros indicamos y ejecutará desde la primera de la lista hasta llegar a la que nosotros especificamos. 
Todas las fases, en nuestro caso, fueron en realidad, solamente una: “pre-clean”. Pero observemos que si en el ejemplo anterior hubiésemos ejecutado “maven clean” le estaríamos indicando a Maven que ejecute su fase “clean” del life cicle asociado, pero en este caso buscaría en su lista de fases y como dijimos ejecutaría todas las previas también, por lo tanto el resultado de esa ejecución seria “pre-clean” y luego “clean”.


Con éste ejemplo cubrimos algunas cuestiones básicas como son la ejecución de tareas de Maven, el orden y precedencia de las fases, ahora agreguemos el concepto de Goals previamente mencionado.


Dijimos que Maven tenia plugins y que Maven por si solo no era nada mas que configuración y una especificación de cómo hacer las cosas, y que el “qué tengo que hacer” era responsabilidad de los diferentes plugins, es aquí donde entra en juego el concepto de goal, un goal es una meta o tarea especifica que tiene un plugin.


Básicamente los plugins realizan diferentes funciones y la forma de identificar y ejecutar cada una de ellas es a través de sus goals.


Ya tenemos un ciclo de vida que tiene un número de fases que se ejecutan en un orden. Por otro lado, plugins que conocen, contienen la lógica que vamos a ejecutar y que tienen goals específicos.


Puntualmente un goal se asocia a una fase esto quiere decir que si ejecutamos una fase Maven ejecutará todos los goals de los diferentes plugins que estén asociados a dicha fase.


En primer lugar una fase no ejecuta nada si no tiene un goal asociado ya que la lógica está en los plugins y no en las fases de Maven.


Otra cuestión interesante es que nosotros podemos decirle a Maven que ejecute una fase o un goal específico, la diferencia será que al decirle que ejecute una fase Maven tendrá en cuenta el ciclo de vida y ejecutará los goals de las fases previas antes de ejecutar los de la fase especificada, en cambio, al decirle que ejecute un goal simplemente ejecutara ese goal sin importar las fases anteriores ni ningún otro goal asociado a la fase especificada.


Veamos un ejemplo de lo mencionado como para ir entiendo un poco la nomenclatura y familiarizarnos con el “lenguaje” de Maven.


Sigamos usando el ciclo de vida Clean. Dijimos que todo pasa por los plugins es por ésto que Maven tiene una serie de plugins que realizan la mayoría de las tareas comunes al desarrollo de un proyecto.


El plugin que está asociado al ciclo de vida Clean es llamado casualmente “CLEAN” y este tiene un goal que se denomina “clean”. Este plugin cuando ejecutamos el goal clean lo que hace es borrar el directorio que Maven genera cuando hacemos un “build”.
Ahora veamos las dos formas de ejecutar el goal clean que teníamos.


La primera sería ejecutando la fase “clean” ya que el goal “clean” está asociado a la “fase clean” del ciclo de vida “Clean”, diciéndole a Maven que ejecute dicha fase (“mvn clean”) lo que hará será previamente ejecutar la fase pre-clean (es decir todos los goals que encuentre asociados a la fase pre-clean) y luego todos los goals asociados a la fase clean.


En el segundo caso al decirle a Maven que ejecute directamente el goal (“mvn clean:clean”) lo que hará será llamar al goal clean de la fase clean del ciclo de vida Clean del plugin CLEAN, sin chequear las fases previas, ni los demás goals asociados.


Veamos ahora un ciclo de vida un poco más complejo y con el que trabajaremos comúnmente.


El ciclo de vida por defecto de Maven

Este ciclo de vida define una serie de pasos que se consideran los comunes para el empaquetado de un proyecto.



Veamos la siguiente tabla (extraída del manual de referencia de Maven) con las fases de este ciclo de vida.
Lifecycle PhaseDescription
ValidateValidate the project is correct and all necessary information is available to complete a build
generate-sourcesGenerate any source code for inclusion in compilation
Process-sourcesProcess the source code, for example to filter any values
generate-resourcesGenerate resources for inclusion in the package
process-resourcesCopy and process the resources into the destination directory, ready for packaging
CompileCompile the source code of the project
Process-classesPost-process the generated files from compilation, for example to do bytecode
 enhancement on Java classes
generate-test-sourcesGenerate any test source code for inclusion in compilation
process-test-sourcesProcess the test source code, for example to filter any values
generate-test-resourcesCreate resources for testing
process-test-resourcesCopy and process the resources into the test destination directory
test-compileCompile the test source code into the test destination directory
TestRun tests using a suitable unit testing framework. These tests should not require the code be packaged or deployed
prepare-packagePerform any operations necessary to prepare a package before the actual packaging. This often results in an unpacked, processed version of the package (coming in Maven 2.1+)
PackageTake the compiled code and package it in its distributable format, such as a JAR, WAR, or EAR
pre-integration-testPerform actions required before integration tests are executed. This may involve things such as setting up the required environment
integration-testProcess and deploy the package if necessary into an environment where integration tests can be run
post-integration-testPerform actions required after integration tests have been executed. This may include cleaning up the environment
VerifyRun any checks to verify the package is valid and meets quality criteria
InstallInstall the package into the local repository, for use as a dependency in other projects locally
DeployCopies the final package to the remote repository for sharing with other developers and projects (usually only relevant during a formal release)






1 comentario: