Otimizando o gerenciamento de dependências com Maven

Uma suite de produtos, com mesmo perfil tecnológico, dividido em vários repositórios, precisa sofrer vários ajustes no classpath para que alguns problemas ou bugs sejam resolvidos. O quê fazer?

Com a ferramenta Maven, a construção de projetos em Java e a manutenção do classpath torna-se algo fácil. Mas mesmo com essa ferramenta, como esses ajustes podem ser feitos? Obviamente todos os projetos devem ser verificados e ajustados. Mas isso pode ser custoso, principalmente se os padrões de organização do pom.xml estiverem muito diferentes.

Matriz de dependências

Uma ótima estratégia seria ter um BOM (Bill of Materials), que é uma matriz de dependências com versões e escopos definidos, pois essa técnica padroniza em um único pom.xml como o classpath deve estar nos projetos que for usado.

Técnicamente o BOM é um pom.xml que é empacotado como tipo ‘pom’, e possui suas definições de dependências dentro do <dependencyManagement>.

Esse BOM pode ser usado de duas maneiras:

  • definindo como parent;
  • ou sendo adicionado no <dependencyManagement> do projeto alvo com o escopo ‘import’ e o tipo ‘pom’.

Abaixo segue exemplos desses dois modos:

<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>com.matera</groupId>
    <artifactId>bom</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>pom</packaging>
    <dependencyManagement>
        <dependencies>
            <!-- #################### -->
            <!-- Loggers -->
            <!-- #################### -->
            <!-- SLF4J API -->
            <dependency>
                <groupId>org.slf4j</groupId>
                <artifactId>slf4j-api</artifactId>
                <version>1.7.7</version>
                <scope>compile</scope>
            </dependency>
            <!-- Logback -->
            <dependency>
                <groupId>ch.qos.logback</groupId>
                <artifactId>logback-classic</artifactId>
                <version>1.1.2</version>
                <scope>runtime</scope>
            </dependency>
            <!-- JCL -->
            <dependency>
                <groupId>commons-logging</groupId>
                <artifactId>commons-logging</artifactId>
                <version>1.2</version>
                <scope>provided</scope>
            </dependency>
            <dependency>
                <groupId>org.slf4j</groupId>
                <artifactId>jcl-over-slf4j</artifactId>
                <version>1.7.7</version>
                <scope>runtime</scope>
            </dependency>
            <!-- LOG4J -->
            <dependency>
                <groupId>log4j</groupId>
                <artifactId>log4j</artifactId>
                <version>1.2.17</version>
                <scope>provided</scope>
            </dependency>
            <dependency>
                <groupId>org.slf4j</groupId>
                <artifactId>log4j-over-slf4j</artifactId>
                <version>1.7.7</version>
                <scope>runtime</scope>
            </dependency>
            <!-- JUL -->
            <dependency>
                <groupId>org.slf4j</groupId>
                <artifactId>jul-to-slf4j</artifactId>
                <version>1.7.7</version>
                <scope>runtime</scope>
            </dependency>
            <!-- Others -->
            <dependency>
                <groupId>org.slf4j</groupId>
                <artifactId>slf4j-simple</artifactId>
                <version>1.7.7</version>
                <scope>provided</scope>
            </dependency>
            <!-- #################### -->
            <!-- JavaEE -->
            <!-- #################### -->
            <dependency>
                <groupId>javax.servlet</groupId>
                <artifactId>javax.servlet-api</artifactId>
                <version>3.1.0</version>
                <scope>provided</scope>
            </dependency>
            <dependency>
                <groupId>javax.servlet</groupId>
                <artifactId>servlet-api</artifactId>
                <version>2.5</version>
                <scope>provided</scope>
            </dependency>
            <!-- #################### -->
            <!-- Test -->
            <!-- #################### -->
            <dependency>
                <groupId>junit</groupId>
                <artifactId>junit</artifactId>
                <version>4.11</version>
                <scope>test</scope>
            </dependency>
            <dependency>
                <groupId>org.mockito</groupId>
                <artifactId>mockito-core</artifactId>
                <version>1.10.8</version>
                <scope>test</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>
    <dependencies>
        <!-- Optional, mandatory dependencies -->
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
        </dependency>
        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-classic</artifactId>
        </dependency>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>jcl-over-slf4j</artifactId>
        </dependency>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>log4j-over-slf4j</artifactId>
        </dependency>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>jul-to-slf4j</artifactId>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
        </dependency>
        <dependency>
            <groupId>org.mockito</groupId>
            <artifactId>mockito-core</artifactId>
        </dependency>
    </dependencies>
</project>
Listagem 1 – matera-bom – as dependências que os projetos devem usar

No BOM acima, note primeiramente que em <dependencyManagement> têm todas as dependências com versão e escopo definidas. Logo abaixo de </dependencyManagement> , tem as definições dos loggers e frameworks de teste, já sem versão e escopo, o que possibilitará aos projetos alvo omitir essas dependências, fazendo com que o classpath obrigatório seja centralizado no BOM.

Parent

Abaixo segue o exemplo de como usar o BOM como herança.

<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>
        <groupId>com.matera</groupId>
        <artifactId>bom</artifactId>
        <version>0.0.1-SNAPSHOT</version>
    </parent>
    <groupId>com.matera</groupId>
    <artifactId>project1</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>jar</packaging>
    <dependencies>
        <!-- #################### -->
        <!-- AWS, with correct loggers-->
        <!-- #################### -->
        <dependency>
            <groupId>com.amazonaws</groupId>
            <artifactId>aws-java-sdk</artifactId>
            <version>1.6.0.1</version>
        </dependency>
    </dependencies>
</project>
Listagem 2 – project1, com o mesmo classpath do BOM, ajustando as dependências de aws-java-sdk

No pom.xml acima, caso não fosse declarado o BOM, a dependência do ‘aws-java-sdk’ traria as dependências do ‘commons-logging’ com o escopo ‘compile’, o que poderia trazer problemas pros outros loggers.

Note que simplemente foi declarado um <parent> com o BOM. Desse modo, todas as dependencias declaradas no BOM, automaticamente são herdadas ao projeto, e as dependências transitivas assumem a nova configuração.

Import

Abaixo segue o exemplo de como usar o BOM como import

<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>com.matera</groupId>
    <artifactId>project2</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>war</packaging>
    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>com.matera</groupId>
                <artifactId>bom</artifactId>
                <version>0.0.1-SNAPSHOT</version>
                <scope>import</scope>
                <type>pom</type>
            </dependency>
        </dependencies>
    </dependencyManagement>
    <dependencies>
        <!-- #################### -->
        <!-- Optional, mandatory classpath configuration -->
        <!-- #################### -->
        <dependency>
            <groupId>com.matera</groupId>
            <artifactId>bom</artifactId>
            <version>0.0.1-SNAPSHOT</version>
            <type>pom</type>
        </dependency>
        <!-- #################### -->
        <!-- JavaEE -->
        <!-- #################### -->
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>javax.servlet-api</artifactId>
        </dependency>
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>servlet-api</artifactId>
        </dependency>
    </dependencies>
</project>
Listagem 3 – project2 – Dependências comuns e ajuste das APIs de servlet como provided

Note que ao invés de usar o <parent> para usar o BOM, agora foi adicionado no <dependencyManagement> com escopo ‘import’ e tipo ‘pom’. Assim, as dependências transitivas (aquelas que são dependências das dependências) que já estão declaradas no BOM, recebem a versão e escopo do BOM.

Também, opicionalmente pode-se adicionar a própria dependência do BOM, como tipo ‘pom’, para herdar as dependências do BOM, assim como na técnica do <parent>. Uma vantagem de se fazer desse modo, que pode-se ter mais de um BOM no projeto alvo.

A configuração do BOM não está limitada a somente adicionar dependencias ou mudar versões e escopos. Pode se fazer isso colocando tal dependência com escopo ‘provided’, como feito para a API de sevlet, no exemplo. Outra opção é fazer exclusões em cada dependencia com a tag <exclusions>.

Como vimos ao longo do post, a forma que BOM é usado pode ajudar ao classpath do projeto a ficar mais organizado e centralizado. Por ficar mais organizado,  automaticamente fará com que a manutenção e custo sejam reduzidos. E por ficar centralizado, erros e bugs conhecidos pode facilmente ser removido em um unico pom.xml. Portanto, caso cuidar do classpath de projetos esteja se tornando um problema, essa técnica será de grande ajuda.

Por IVAN RAFAEL MATINADO

Postado em: 31 de janeiro de 2015

Confira outros artigos do nosso blog

REST não é JSON

21 de agosto de 2017

Bruno Sofiato

[Webinar] Profile de aplicações Java com Oracle Mission Control e Flight Recorder

24 de julho de 2017

Danival Calegari

Criando Mocks de serviços REST com SoapUI

27 de junho de 2017

Monise Costa

JavaScript 6: diferença entre var, let e const

09 de maio de 2017

Otávio Felipe do Prado

Deixe seu comentário