Princípios de um bom build versus Maven

zecaurubu

Uma das discussões frequentes em minha empresa é sobre colocar ou não jars no repositório e sobre o polêmico Maven. Para quem não conhece Maven é uma ferramenta de build da apache que se tornou bastante popular nos últimos anos, especialmente na comunidade java. A ferramenta se propõe a gerenciar dependências, facilitar a escrita de arquivos de build usando conceitos de convenção sobre configuração e auxiliar em todo o ciclo de construção do projeto.

Apesar da proposta atraente, algumas idéias por trás do Maven violam princípios importantes de um bom sistema de build. Nosso companheiro Zeca Urubu irá nos ajudar a demonstrar alguns desses princípios.

É uma questão de princípios

Reproducibilidade

Reproducibilidade

Um dos itens importantes de um sistema de build é a reproducibilidade. Isso é, dada uma determinada revisão de código, o binário (ou equivalente) gerado deve ser sempre o mesmo, independente da máquina utilizada ou do momento em que o build foi executado.

Uma das questões com o Maven é que ao executar um build ele sempre irá buscar uma versão mais atualizada dos seus plugins principais (compiler, surefire, ear, war, etc.). Isso significa que o resultado do build pode variar de acordo com a disponibilização de novas versões no repositório, mesmo você não fazendo nenhuma alteração no seu projeto nem na versão do Maven.

Como consequência, ora o binário pode ser gerado de uma forma, ora de outra sem nenhuma explicação aparente [1].

Dependência externa

dependencia

O Maven depende de um repositório externo para executar suas funções. De uma forma geral esse repositório não está sob o controle do desenvolvedor ou da empresa em questão, e não existe nenhuma garantia que vai continuar no ar, ficar disponível ou que não será alterado.

Isso é pouco aceitável do ponto de vista de risco, pois um processo crítico do projeto depende fundamentalmente de um servidor e de dados sobre os quais não se tem controle nenhum. É possível mitigar esse risco usando um repositório local como o Archiva, a um custo de ter que gerenciar o repositório manualmente.

Baselines

Uma outra questão importante dentro do ciclo de vida de um projeto são as baselines ou tags. As tags registram o estado de um projeto em um determinado momento, tornando possível fazer modificações a partir de uma base de código conhecida ou investigar problemas que acontecem em uma versão que está em produção.

Uma das desvantagens ao usar um gerenciador de dependências à la Maven, é que não existe garantia que um build gerado a partir de uma tag sempre irá gerar um binário igual. Um motivo foi descrito no tópico acima "reproducibilidade", porém existem outros fatores que contribuem para esse problema dentro da filosofia da ferramenta.

Um deles está relacionado às dependências transitivas (dependências transitivas basicamente são as dependências das suas dependências). As dependências transitivas normalmente não são declaradas no projeto, assim dependendo do repositório que for usado, os jars baixados podem vir diferentes, reduzindo a confiabilidade no build [2].

Histórico e rastreabilidade

Um outro ponto é que como os artefatos dos projetos vêm de duas fontes diferentes (repositório do maven e repositório de código) não é possível relacionar uma alteração de dependência com uma determinada revisão do código, já que o repositório do maven pode ser alterado isoladamente.

Como uso importante da rastreabilidade podemos citar as ferramentas de integração contínua. A maioria delas relacionam mudança de código com o o build em questão para caso um testes falhe seja possível descobrir facilmente que alteração no projeto causou o erro. Como ao usar o maven a rastreabilidade das dependências é perdida, um build pode quebrar sem ter como registrar exatamente o motivo ou alteração.

Velocidade

Para equipes praticando integração contínua o tempo de feedback é um fator fundamental.

Um build Maven executa cerca de 20% mais lento que uma ferramenta similar como o Ant. Isso se deve principalmente à necessidade de conexão com a rede/internet [3].

Também é uma questão de implementação

Apesar de não termos o Zeca Urubu para ajudar com as questões de implementação do Maven, vamos usar vários exemplos práticos.

XML de configuração verboso

O xml de configuração é verboso e difícil de manter. Ele poderia ser muito mais simples se fossem usados atributos ao invés de elementos. Compare uma declaração de dependência do Ivy com a do Maven.

Ivy

<dependency org="commons-lang" name="commons-lang"  
rev="2.0"/>  

Maven

<dependency>  
 <groupId>commons-lang</groupId>
 <artifactId>commons-lang</artifactId>
 <version>2.0</version>
 </dependency>

Agora compare a execução de um script sql com Maven e com o ant.

Ant

<sql driver="oracle.jdbc.OracleDriver"  
  url="jdbc:oracle:thin:@host:1521:xe" userid="sa"
  password="sa" onerror="continue">
  <classpath>
     <fileset dir="../driverfolder/lib"
      includes="**/*.jar" />
  </classpath>
  <transaction src="create.sql"/>
  <transaction src="drop.sql"/>
</sql>  

Maven

<plugin>  
    <groupId>org.codehaus.mojo</groupId>
    <artifactId>sql-maven-plugin</artifactId>
    <version>1.1</version>

    <dependencies>
        <dependency>
            <groupId>com.oracle</groupId>
            <artifactId>ojdbc14</artifactId>
            <version>9.0.2.0.0</version>
        </dependency>
    </dependencies>

    <configuration>
      <driver>oracle.jdbc.OracleDriver</driver>
      <url>jdbc:oracle:thin:@niva:1521:xe</url>
      <username>sa</username>
      <password>sa</password>
    </configuration>

    <executions>
      <execution>
        <phase>pre-integration-test</phase>
        <goals>
          <goal>execute</goal>
        </goals>
        <configuration>
          <srcFiles>
            <srcFile>drop.sql</srcFile>
            <srcFile>create.sql</srcFile>
          </srcFiles>
        </configuration>
      </execution>
    </executions>
</plugin>  

Repositório central bagunçado

O repositório central é bagunçado, existem entradas duplicadas e entradas que referenciam JARs que não existem. Frequentemente o mesmo fica fora do ar ou com lentidão.

Além disso, por questões de licença, alguns jars não podem ser disponibilizados no repositório central.

Dependências desnecessárias e duplicadas

Muitas vezes dependências desnecessárias ou duplicadas (ex. várias versões do mesmo jar ou a mesma versão com nomes diferentes) são trazidos para o projeto. A ferramenta disponibiliza meios para contornar problema através de excludes. Mas resolver o problema de dependências não seria um dos propósitos principais de se usar um gerenciador de dependências?

Documentação de má qualidade

Existe um livro grátis com cerca de 500 páginas que descreve bem a ferramenta em si (better builds with maven). Porém a documentação dos plugins de uma forma geral é de má qualidade, mesmo quando existe uma documentação original da tarefa que no caso seria só copiar.

Tarefa Ant do XML Beans


















Attribute Description
noann Skip over schema elements. Default is false.
javaSource Generate java source compatible with the given version. Currently only "1.4" and "1.5" are supported. Default is "1.4".

Plugin do Maven do XML Beans






















Name Description
noAnnon Ignore annotations.
javaSource Returns the javasource parameter which specifies an option to the XmlBeans code generator.
jaxb Todo: Unkown use.

Reinvenção da roda

As tarefas que os plugins executam normalmente são reescritas para o Maven, mesmo que exista uma task ant disponibilizada pela apache ou por fornecedores executando a mesma tarefa. Isso faz com que bugs que não existiam originalmente sejam introduzidos no Maven e features suportadas pela tarefa original não sejam suportadas pelo Maven.

Exemplo de alguns casos:





















Tarefa Características suportadas pela tarefa original Features suportadas pelo maven
XMLBeans 33 44
Weblogic deploy 40 24

Complementos


[1] Existe um branch não oficial disponibilizado por um contribuidor do projeto que tenta contornar esse problema.
[2] Me lembro uma vez que fui gerar um pacote no ambiente do cliente e o Maven baixou uma dependência diferente do hibernate que conflitava com o framework MVC que usávamos. O resultado foi que no pacote gerado nada funcionava.
[3] Apesar de ser possível diminuir essa diferença rodando o script em modo offline (passando parâmetro -o), nesse caso não existe garantia que o build irá funcionar, pois algumas dependências podem não existir localmente