quinta-feira, 10 de janeiro de 2013

Criando um sistema multi-módulos com o Maven


Nota: Uma versão atualizada e melhorada desse post está disponível no meu novo blog, com o título Criando uma aplicação multimódulo com Maven. Não deixe de conferir!!!


Imagine que você está construindo um sistema grande, composto de diversos projetos no seu IDE favorito. Cada um desses projetos tem a sua configuração Maven específica para fazer a compilação e geração do seu Jar, War, Ear ou algo similar. Apesar dessa abordagem ser satisfatória ela pode ter algumas desvantagens, tais como:

1) Cada projeto tem uma configuração totalmente separada e, caso exista algo em comum entre esses projetos, essa configuração deverá ser replicada;
2) Quando o sistema com todos os seus projetos tiverem que ser compilados, será necessário compilar cada um dos projetos considerando a dependência entre eles. E essa dependência geralmente está na cabeça de algumas pessoas que nunca estão disponíveis no momento.

Uma forma de resolver essas questões consiste em reogarnizar os projetos na estrutura multi-módulo do Maven. Nessa abordagem, cada projeto é definido como um módulo e é criado um projeto, que chamaremos de projeto agregador, utilizado agrupar todos os módulos. O projeto agregador contém um pom.xml com declarações comuns, tais como dependências e plugins, que são herdadas por cada módulo.

Para exemplificar como é essa organização multi-módulos utilizaremos um sistema fictício chamado SistemaX. Esse sistema contém dois módulos criativamente chamados de Módulo A e Módulo B. Só para complicar um pouquinho, vamos supor que o Módulo A tem uma dependência para o JUnit e o Módulo B depende do Módulo A. A estrutura de diretórios deve ficar da seguinte forma:

sistemaX/
├── moduloA
│   └── pom.xml
├── moduloB
│   └── pom.xml
└── pom.xml
O diretório sistemaX é onde está definido o projeto agregador e os diretórios dos módulos A e B são, respectivamente, moduloA e moduloB. Observe que os módulos devem ficar dentro do diretório do projeto agregador. Cada módulo continua com o seu pom.xml específico porém, agora eles devem herdar as definições feitas no pom do projeto agregador. Vamos analisar cada um dos arquivos pom.xml e explicar como são feitas as configurações.

sistemaX/pom.xml


<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/maven-v4_0_0.xsd">
  <modelVersion>4.0.0</modelVersion>

  <groupId>meu.projeto.multimodulo</groupId>
  <artifactId>sistemaX</artifactId>
  <packaging>pom</packaging>
  <version>1.0</version>

  <modules>
    <module>moduloA</module>
    <module>moduloB</module>
  </modules>

  <dependencyManagement>
    <dependencies>
      <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.8.1</version>
        <scope>test</scope>
      </dependency>
    </dependencies>
  </dependencyManagement>
</project>
No pom.xml do projeto agregador são definidas as características que basicamente todo pom.xml define, tais como: groupId, artifactId, packaging e version. Um detalhe aqui é que o packaging está definido como pom. Isso indica que esse projeto não gera um artefato tipo jar ou war, e sim que ele contém definições que serão herdadas por outros projetos. A tag <modules> é usada para definir os módulos que fazem parte do projeto, nesse caso, moduloA e moduloB. Além da definição dos módulos, também são declaradas algumas dependências com a tag <dependencyManagement>. Apesar do projeto agregador em sí não precisar dessas dependências, ele pode ser usado para manter um ponto central de gerenciamento dessas definições ao invés de fazer separado por módulo.

sistemaX/moduloA/pom.xml


<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/maven-v4_0_0.xsd">
  <modelVersion>4.0.0</modelVersion>

  <parent>
    <groupId>meu.projeto.multimodulo</groupId>
    <artifactId>sistemaX</artifactId>
    <version>1.0</version>
  </parent>

  <artifactId>moduloA</artifactId>
  <packaging>jar</packaging>

  <dependencies>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
    </dependency>
  </dependencies>
</project>


No pom.xml do Módulo A é utilizada a tag <parent> para informar que as definições feitas no pom.xml do projeto agregador são utilizadas nesse módulo. Observe que aqui não é mais necessário definir um groupId e version para o módulo pois essas informações são obtidas a partir do pom do projeto agregador. No caso desse módulo a tag <packaging> é definida como jar pois o módulo vai gerar um jar específico, o mesmo vale o módulo B, como veremos a seguir. Como esse módulo depende do JUnit, é criada uma entrada especificando essa dependência, mas repare que não é mencionada qual a versão do JUnit será usadao pois isso é obtido na definição da dependência no pom do projeto agregador.

sistemaX/moduloB/pom.xml


<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/maven-v4_0_0.xsd">
  <modelVersion>4.0.0</modelVersion>

  <parent>
    <groupId>meu.projeto.multimodulo</groupId>
    <artifactId>sistemaX</artifactId>
    <version>1.0</version>
  </parent>

  <artifactId>moduloB</artifactId>
  <packaging>jar</packaging>

  <dependencies>
    <dependency>
      <groupId>meu.projeto.multimodulo</groupId>
      <artifactId>moduloA</artifactId>
      <version>1.0</version>
    </dependency>
  </dependencies>
</project>



Note que o pom.xml do Módulo B é muito parecido com o do Módulo A, exceto pelo fato dele definir uma dependência para o Módulo A mas não para o JUnit. Nesse caso, se existir alguma classe que faça uso do JUnit no Módulo B, será gerado um erro de compilação pois a dependência não foi declarada para esse módulo, mesmo que ela tenha sido definida no pom do projeto agregador.

Pronto, feitas essas alterações basta executar o maven a partir do diretório sistemaX que ele se encarregará de compilar os projetos dependentes e gerar os respectivos artefatos que forem necessário.

Um último detalhe. Estão lembrados que o Módulo B depende do Módulo A? Assim, o Módulo A tem que ser compilado antes do Módulo B. E se no pom.xml do projeto agregador invertermos a ordem de declaração dos módulos, deixando da seguinte forma:

  <modules>
    <module>moduloB</module>
    <module>moduloA</module>
  </modules>


Bem, isso não afeta em nada! O Maven é esperto o suficiente para decifrar a dependência entre os módulos e fazer a compilação na ordem correta.

É isso ai!

Bons builds para todos.