20120304

Maven : proyecto web modular

En el trabajo siempre tengo un proyecto web entre manos, sea cual sea la funcionalidad o el cliente. Con suerte, ese proyecto se construye con maven, está estructurado y es fácil de mantener.
Una de las cosas que me ocurren es que, a la hora de realizar una nueva funcionalidad, el despliegue sobre el sistema ya en producción debe ser parcial. No se envía el WAR directamente, sino se que envía un "parche" con los ficheros (jsp, class, configuración,...) que se hayan modificado, a descomprimir en el servidor de producción.

Para evitar enviar multitud de ficheros class, lo mejor es que todas las clases del proyecto se empaqueten en un JAR, de forma que, si se modifica cualquier clase, debe generarse de nuevo el JAR y enviarlo como parte del parche a desplegar en producción.

Viendo la generación de un WAR con maven, he encontrado la opción de generar el JAR de las clases de forma separada, usando la configuración del plugin maven-war-plugin:

<plugin>
     <artifactId>maven-war-plugin</artifactId>
     <version>2.2</version>
     <configuration>
         <archiveClasses>true</archiveClasses>
     </configuration>
</plugin>

Al generar el artefacto, se genera el WAR, y en el /WEB-INF/lib de la aplicación, nos encontramos con un JAR con las clases del proyecto. El directorio /WEB-INF/classes estará vacío.
El problema de esto es que algunos ficheros de configuración suelen estar en /WEB-INF/classes, como por ejemplo el fichero log4j.xml. Generando de la forma indicada, este fichero está contenido en el JAR resultante, y ahí tenemos un problema, ya que la configuración de los logs es totalmente dependiente del entorno de despliegue.

Así que la mejor solución, como casi siempre, es la que recomiendan los chicos de maven : http://maven.apache.org/plugins/maven-war-plugin/faq.html#attached

Básicamente nos insta a crear un proyecto que contenga las clases y otro que contenga la parte web de la vista. De esta forma, podemos meter el los resources del proyecto web ficheros como log4j.xml, que terminarán en /WEB-INF/classes, y las clases en el proyecto que se genera como JAR, de forma que terminará estando en /WEB-INF/lib.

Al final, tendremos un proyecto pom padre, al que he llamado "mvnweb", y dos módulos hijos "mvnweb-classes" y "mvnweb-web". Como sus nombres indican, mvnweb-classes contiene las clases del proyecto y mvnweb-web la parte web del mismo. Añadimos una dependencia de mvnweb-classes en mvnweb-web y, al empaquetar el WAR, obtenemos lo deseado, siempre y cuando los ficheros dependientes de la configuración del entorno de despliegue estén en el proyecto mvnweb-web.

Éste es el pom del proyecto padre mvnweb:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>es.org.nms</groupId>
  <artifactId>mvnweb</artifactId>
  <version>0.0.1-SNAPSHOT</version>
  <packaging>pom</packaging>
  <modules>
      <module>mvnweb-classes</module>
    <module>mvnweb-web</module>
  </modules>
</project>

Éste es el pom de mvnweb-classes

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <parent>
    <artifactId>mvnweb</artifactId>
    <groupId>es.org.nms</groupId>
    <version>0.0.1-SNAPSHOT</version>
    <relativePath>..</relativePath>
  </parent>
  <artifactId>mvnweb-classes</artifactId>
</project>

Y finalmente el pom de mvnweb-web, que contiene la dependencia con mvnweb-classes.

<?xml version="1.0"?>
<project
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"
    xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <artifactId>mvnweb</artifactId>
        <groupId>es.org.nms</groupId>
        <version>0.0.1-SNAPSHOT</version>
    </parent>
    <groupId>es.org.nms</groupId>
    <artifactId>mvnweb-web</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>war</packaging>
    <name>mvnweb-web Maven Webapp</name>
    <url>http://maven.apache.org</url>
    <dependencies>
        <dependency>
            <groupId>es.org.nms</groupId>
            <artifactId>mvnweb-classes</artifactId>
            <version>0.0.1-SNAPSHOT</version>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.4</version>
            <scope>test</scope>
        </dependency>
    </dependencies>
    <build>
        <finalName>mvnweb-web</finalName>
    </build>
</project>


* Nota : está claro que la mejor opción para esto es tener un profile para generar un WAR para producción, pero la mayoría de las veces, al menos en mi caso, no es posible tener toda la configuración (conexiones a base de datos, usuarios y passwords) debido a razones de seguridad o a no disponer de esos datos.