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.

11 comentários:

  1. Esclareceu bastante coisa. Ótimo tutorial, parabéns.

    ResponderExcluir
  2. Olá

    caso eu deseje colocar um projeto web no local jar eu devo declarar war.

    outra dúvida ele gera toda a estrutra dos módulos? ou devo criar tudo na mão?

    ResponderExcluir
    Respostas
    1. Oi Maurício, tudo bem?

      Isso aí, caso você precise criar um projeto web, basta declarar o pom do módulo Web como web ao invés jar. Mas lmebre-se que o plugin do Maven para web espera encontrar nesse módulo uma estrutura de arquivos contendo o WEB-INF/web.xml.

      Agora quanto à geração da estrutura de diretórios você pode usar algum Archetype do Maven pra gerar um esqueleto de projeto para você. Mas mesmo assim você terá que fazer isso para cada módulo e depois alterar os POMs para que eles funcionem como um projeto multi-módulo.

      Excluir
  3. como faço para que nesta situação caso eu tenha uma dependencia no modula A (hibernate), ele coloque no projeto principal SistemaX, no classpath do eclipse esta dependencia

    ResponderExcluir
    Respostas
    1. Opa, tudo bem?

      Você não vai conseguir colocar a dependência direto no sistemaX porque mesmo que você coloque no pom.xml, na hora de carregar o projeto no Eclipse ele não criará o .classpath desse projeto, assim, não tem como você configurar isso. Mas uma outra questão é que o correto é colocar as dependências direto nos módulos que fazem uso delas e não no projeto principal, que é considerado simplesmente um agregador dos módulos.

      []'s.

      Excluir
  4. Meus parabéns! Este artigo salvou meu dia! Muito obrigado.

    ResponderExcluir
  5. Cara, muito legal esse artigo mesmo! Vlw

    ResponderExcluir
  6. Muito bom! Meus parabéns, tirou todas minhas duvidas.

    ResponderExcluir
  7. Parabéns, boa abstração, sem papo furado

    ResponderExcluir
  8. Como fazer o build desses modulos ?
    clean install ....

    ResponderExcluir
    Respostas
    1. Oi Leandro, tudo bem? Você pode executar um `clean install` na raiz do projeto mas depois para executar a aplicação você precisa estar no módulo específico. Por exemplo, se um módulo gera um JAR e outro WAR, você deve executar a partir do módulo que gera o WAR (por exemplo, se vc estiver usando um plugin do jetty, entre no módulo do WAR e executar `mvn jetty:run`).

      A propósito, eu tenho uma versão atualizada desse post no meu novo blog, que explico isso com um exemplo um pouco melhor e tem o código fonte disponível também. Esse novo post está aqui:

      http://thecodeinside.com/criando-uma-aplicacao-multimodulo-com-maven/

      Excluir