"Você torna o mundo um lugar melhor fazendo melhorias diárias para se tornar a melhor versão de si mesmo". Roy T. Bennett, The Light in the Heart
Nesse terceiro posts falaremos sobre o gerenciamento de versões utilizando o Gradle Versions Plugin do Ben Manes. Isso nos ajudará a manter nosso projeto com as dependências atualizadas.
Discutiremos também os problemas que isso pode causar, e o porque não termos um fluxo de Continuous Integration pode nos dar grandes dores de cabeça.
Por fim, falarei do Dependabot. Não apliquei nesse projeto, mas vou colocar no backlog, e quem sabe escrever em um outro post.
Esse artigo faz parte de uma série, abaixo é possível encontrar a lista completa de artigos.
Estamos nos baseando no curso Desenvolvimento web com Quarkus do @viniciusfcf.
O repositório que estamos utilizando é:
O tempo passa, o tempo voa
O tempo é implacável! Mudanças acontecem constantemente, e isso impacta decisões passadas que tomamos.
No mundo do desenvolvimento de software essa máxima permanece! Bibliotecas e frameworks são atualizados ou descontinuados; outras vezes podemos descobrir uma vulnerabilidade nas nossas dependências e sermos forçados a realizar mudanças (falaremos um pouco disso em um post futuro chamado Plugins do Gradle: Validação de vulnerabilidades de dependências com OWASP Dependency Check).
Uma das primeiras ideias que temos é: Vamos deixar o nosso projeto sempre com a última versão das bibliotecas disponíveis!
"Para todo problema complexo existe sempre uma solução simples, elegante e completamente errada". H L Mencken
Qual é o problema podemos ter nessa primeira abordagem?
Para atualizar uma dependência em um projeto é importante termos algumas coisas em mente, como:
- Existe uma equipe de sustentação que precisa garantir que as bibliotecas que estamos usando estão em conformidade com uma arquitetura corporativa?
- As bibliotecas que estamos usando funcionam corretamente se todas forem atualizadas para sua última versão?
- O servidor em que a solução será implantada tem suporte a tais bibliotecas?
- dentre outros.
Nesse post falarei de uma estratégia para essa atualização, porém, é importante ressaltar que isso não é a bala de prata para todos os projetos, e que sempre é bom acordar as decisões com o time, equipe de arquitetura e/ou liderança técnica responsável.
Utilizando um plugin
Um plugin bem interessante do Gradle é o Gradle Versions Plugin do Ben Manes. Sua aplicação é bem simples.
Utilizando o nosso projeto de referência, no nosso arquivo build.gradle na raiz do projeto podemos ver algo como o seguinte:
plugins {
...
id "com.github.ben-manes.versions" version "$versionsVersion" apply false
...
}
...
subprojects {
if (it.name != 'applications') {
...
apply from: "$rootDir/plugins/docs.gradle"
...
}
}
As configurações do arquivo plugins/docs.gradle serão melhor explicadas adiante.
Caso você esteja com dúvida sobre esses $ no código, dá uma olhada no post anterior em que falamos sobre Variáveis de projeto.
Essa primeira parte da configuração nos ajuda a buscar as atualizações de todas as nossas bibliotecas através da seguinte task do Gradle:
foo@bar:~$ ./gradlew dependencyUpdates
Executando a task vamos ter um resultado semelhante a:
./gradlew dependencyUpdates
------------------------------------------------------------
:applications:cadastro Project Dependency Updates (report to plain text file)
------------------------------------------------------------
The following dependencies are using the latest milestone version:
- com.approvaltests:approvaltests:18.5.0
- com.github.database-rider:rider-cdi:1.35.0
- com.github.database-rider:rider-core:1.35.0
- com.google.code.gson:gson:2.10
- org.mapstruct:mapstruct:1.5.3.Final
- org.mapstruct:mapstruct-processor:1.5.3.Final
- stax:stax:1.2.0
The following dependencies have later milestone versions:
- io.opentracing.contrib:opentracing-jdbc [0.2.4 -> 0.2.15]
https://github.com/opentracing-contrib/java-jdbc
- io.quarkus:io.quarkus.gradle.plugin [2.14.3.Final -> 3.0.0.Alpha2]
- io.quarkus:quarkus-arc [2.14.3.Final -> 3.0.0.Alpha2]
https://github.com/quarkusio/quarkus
- io.quarkus:quarkus-arc-deployment [2.14.3.Final -> 3.0.0.Alpha2]
https://github.com/quarkusio/quarkus
- io.quarkus:quarkus-config-yaml [2.14.3.Final -> 3.0.0.Alpha2]
https://github.com/quarkusio/quarkus
- io.quarkus:quarkus-config-yaml-deployment [2.14.3.Final -> 3.0.0.Alpha2]
https://github.com/quarkusio/quarkus
- io.quarkus:quarkus-core-deployment [2.14.3.Final -> 3.0.0.Alpha2]
https://github.com/quarkusio/quarkus
- io.quarkus:quarkus-flyway [2.14.3.Final -> 3.0.0.Alpha2]
https://github.com/quarkusio/quarkus
- io.quarkus:quarkus-flyway-deployment [2.14.3.Final -> 3.0.0.Alpha2]
https://github.com/quarkusio/quarkus
- io.quarkus:quarkus-hibernate-orm-panache [2.14.3.Final -> 3.0.0.Alpha2]
https://github.com/quarkusio/quarkus
- io.quarkus:quarkus-hibernate-orm-panache-deployment [2.14.3.Final -> 3.0.0.Alpha2]
https://github.com/quarkusio/quarkus
- io.quarkus:quarkus-hibernate-validator [2.14.3.Final -> 3.0.0.Alpha2]
https://github.com/quarkusio/quarkus
- io.quarkus:quarkus-hibernate-validator-deployment [2.14.3.Final -> 3.0.0.Alpha2]
https://github.com/quarkusio/quarkus
- io.quarkus:quarkus-jdbc-postgresql [2.14.3.Final -> 3.0.0.Alpha2]
https://github.com/quarkusio/quarkus
- io.quarkus:quarkus-jdbc-postgresql-deployment [2.14.3.Final -> 3.0.0.Alpha2]
https://github.com/quarkusio/quarkus
- io.quarkus:quarkus-junit5 [2.14.3.Final -> 3.0.0.Alpha2]
https://github.com/quarkusio/quarkus
- io.quarkus:quarkus-resteasy-jsonb [2.14.3.Final -> 3.0.0.Alpha2]
https://github.com/quarkusio/quarkus
- io.quarkus:quarkus-resteasy-jsonb-deployment [2.14.3.Final -> 3.0.0.Alpha2]
https://github.com/quarkusio/quarkus
- io.quarkus:quarkus-smallrye-jwt [2.14.3.Final -> 3.0.0.Alpha2]
https://github.com/quarkusio/quarkus
- io.quarkus:quarkus-smallrye-jwt-deployment [2.14.3.Final -> 3.0.0.Alpha2]
https://github.com/quarkusio/quarkus
- io.quarkus:quarkus-smallrye-metrics [2.14.3.Final -> 3.0.0.Alpha2]
https://github.com/quarkusio/quarkus
- io.quarkus:quarkus-smallrye-metrics-deployment [2.14.3.Final -> 3.0.0.Alpha2]
https://github.com/quarkusio/quarkus
- io.quarkus:quarkus-smallrye-openapi [2.14.3.Final -> 3.0.0.Alpha2]
https://github.com/quarkusio/quarkus
- io.quarkus:quarkus-smallrye-openapi-deployment [2.14.3.Final -> 3.0.0.Alpha2]
https://github.com/quarkusio/quarkus
- io.quarkus:quarkus-smallrye-opentracing [2.14.3.Final -> 3.0.0.Alpha2]
https://github.com/quarkusio/quarkus
- io.quarkus:quarkus-smallrye-opentracing-deployment [2.14.3.Final -> 3.0.0.Alpha2]
https://github.com/quarkusio/quarkus
- io.quarkus.platform:quarkus-bom [2.14.3.Final -> 3.0.0.Alpha2]
https://github.com/quarkusio/quarkus-platform
- io.rest-assured:rest-assured [4.5.1 -> 5.3.0]
http://code.google.com/p/rest-assured
- org.testcontainers:postgresql [1.17.5 -> 1.17.6]
https://testcontainers.org
Gradle release-candidate updates:
- Gradle: [7.6: UP-TO-DATE]
------------------------------------------------------------
Nesse caso, removi os logs referentes aos demais subprojetos só para termos um resultado menor.
Podemos ver por exemplo que as seguintes dependências estão atualizadas no nosso projeto:
The following dependencies are using the latest milestone version:
- com.approvaltests:approvaltests:18.5.0
- com.github.database-rider:rider-cdi:1.35.0
- com.github.database-rider:rider-core:1.35.0
- com.google.code.gson:gson:2.10
- org.mapstruct:mapstruct:1.5.3.Final
- org.mapstruct:mapstruct-processor:1.5.3.Final
- stax:stax:1.2.0
E que a dependência do opentracing-jdbc está desatualizada:
The following dependencies have later milestone versions:
- io.opentracing.contrib:opentracing-jdbc [0.2.4 -> 0.2.15]
No entanto, temos um efeito colateral da forma como o plugin está funcionando. E isso pode ser visto em todas as dependências do Quarkus que estamos usando, a sugestão de atualização para versão 3.0.0.Alpha2:
- io.quarkus:io.quarkus.gradle.plugin [2.14.3.Final -> 3.0.0.Alpha2]
Na maioria das vezes não queremos bibliotecas de Release, Alpha ou Beta no nosso projeto por não serem versões finais, e poderem conter alterações futuras. Para resolver esse problema, o próprio plugin nos oferece uma solução em suas configurações. No nosso caso, podemos encontrar elas em plugins/docs.gradle; eu havia comentado elas para evidenciar o problema anterior:
apply plugin: "com.github.ben-manes.versions"
...
dependencyUpdates {
checkForGradleUpdate = true
outputFormatter = "json"
outputDir = "build/reports/docs/dependencyUpdates"
reportfileName = "report"
resolutionStrategy = {
componentSelection { rules ->
rules.all { ComponentSelection selection ->
boolean rejected = ["alpha", "beta", "rc", "cr", "m"].any { qualifier ->
selection.candidate.version ==~ /(?i).*[.-]${qualifier}[.\d-]*/
}
if (rejected) {
selection.reject("Release candidate")
}
}
}
}
}
Executando novamente a task, nosso resultado para :applications:cadastro é o seguinte:
./gradlew dependencyUpdates
------------------------------------------------------------
:applications:cadastro Project Dependency Updates (report to plain text file)
------------------------------------------------------------
The following dependencies are using the latest milestone version:
- com.approvaltests:approvaltests:18.5.0
- com.github.database-rider:rider-cdi:1.35.0
- com.github.database-rider:rider-core:1.35.0
- com.google.code.gson:gson:2.10
- io.quarkus.platform:quarkus-bom:2.14.3.Final
- org.mapstruct:mapstruct:1.5.3.Final
- org.mapstruct:mapstruct-processor:1.5.3.Final
- stax:stax:1.2.0
The following dependencies have later milestone versions:
- io.opentracing.contrib:opentracing-jdbc [0.2.4 -> 0.2.15]
https://github.com/opentracing-contrib/java-jdbc
- io.quarkus:io.quarkus.gradle.plugin [2.14.3.Final -> 2.15.0.Final]
- io.quarkus:quarkus-arc [2.14.3.Final -> 2.15.0.Final]
https://github.com/quarkusio/quarkus
- io.quarkus:quarkus-arc-deployment [2.14.3.Final -> 2.15.0.Final]
https://github.com/quarkusio/quarkus
- io.quarkus:quarkus-config-yaml [2.14.3.Final -> 2.15.0.Final]
https://github.com/quarkusio/quarkus
- io.quarkus:quarkus-config-yaml-deployment [2.14.3.Final -> 2.15.0.Final]
https://github.com/quarkusio/quarkus
- io.quarkus:quarkus-core-deployment [2.14.3.Final -> 2.15.0.Final]
https://github.com/quarkusio/quarkus
- io.quarkus:quarkus-flyway [2.14.3.Final -> 2.15.0.Final]
https://github.com/quarkusio/quarkus
- io.quarkus:quarkus-flyway-deployment [2.14.3.Final -> 2.15.0.Final]
https://github.com/quarkusio/quarkus
- io.quarkus:quarkus-hibernate-orm-panache [2.14.3.Final -> 2.15.0.Final]
https://github.com/quarkusio/quarkus
- io.quarkus:quarkus-hibernate-orm-panache-deployment [2.14.3.Final -> 2.15.0.Final]
https://github.com/quarkusio/quarkus
- io.quarkus:quarkus-hibernate-validator [2.14.3.Final -> 2.15.0.Final]
https://github.com/quarkusio/quarkus
- io.quarkus:quarkus-hibernate-validator-deployment [2.14.3.Final -> 2.15.0.Final]
https://github.com/quarkusio/quarkus
- io.quarkus:quarkus-jdbc-postgresql [2.14.3.Final -> 2.15.0.Final]
https://github.com/quarkusio/quarkus
- io.quarkus:quarkus-jdbc-postgresql-deployment [2.14.3.Final -> 2.15.0.Final]
https://github.com/quarkusio/quarkus
- io.quarkus:quarkus-junit5 [2.14.3.Final -> 2.15.0.Final]
https://github.com/quarkusio/quarkus
- io.quarkus:quarkus-resteasy-jsonb [2.14.3.Final -> 2.15.0.Final]
https://github.com/quarkusio/quarkus
- io.quarkus:quarkus-resteasy-jsonb-deployment [2.14.3.Final -> 2.15.0.Final]
https://github.com/quarkusio/quarkus
- io.quarkus:quarkus-smallrye-jwt [2.14.3.Final -> 2.15.0.Final]
https://github.com/quarkusio/quarkus
- io.quarkus:quarkus-smallrye-jwt-deployment [2.14.3.Final -> 2.15.0.Final]
https://github.com/quarkusio/quarkus
- io.quarkus:quarkus-smallrye-metrics [2.14.3.Final -> 2.15.0.Final]
https://github.com/quarkusio/quarkus
- io.quarkus:quarkus-smallrye-metrics-deployment [2.14.3.Final -> 2.15.0.Final]
https://github.com/quarkusio/quarkus
- io.quarkus:quarkus-smallrye-openapi [2.14.3.Final -> 2.15.0.Final]
https://github.com/quarkusio/quarkus
- io.quarkus:quarkus-smallrye-openapi-deployment [2.14.3.Final -> 2.15.0.Final]
https://github.com/quarkusio/quarkus
- io.quarkus:quarkus-smallrye-opentracing [2.14.3.Final -> 2.15.0.Final]
https://github.com/quarkusio/quarkus
- io.quarkus:quarkus-smallrye-opentracing-deployment [2.14.3.Final -> 2.15.0.Final]
https://github.com/quarkusio/quarkus
- io.rest-assured:rest-assured [4.5.1 -> 5.3.0]
http://code.google.com/p/rest-assured
- org.testcontainers:postgresql [1.17.5 -> 1.17.6]
https://testcontainers.org
Gradle release-candidate updates:
- Gradle: [7.6: UP-TO-DATE]
------------------------------------------------------------
Problema de atualização de bibliotecas resolvido? É só ir no nosso gradle.properties e pronto.
O que fizemos foi apenas uma forma de evidenciar quais as bibliotecas que estão desatualizadas.
Antes de mudar as versões de todas as que estão aparecendo como desatualizadas é importante validarmos se isso não impacta as outras bibliotecas do nosso projeto. Em muitos casos, uma biblioteca utiliza outras bibliotecas, o que chamamos de dependências transitivas. Com isso, para que uma biblioteca funcione corretamente é importante que suas dependências transitivas também estejam.
Pegue por exemplo a Spring Boot Starter Data JPA. Não vou me lembrar da versão exata, mas o Java teve uma grande mudança na versão 8, dentre elas, a adição de Optional.
Me lembro quando utilizei o plugin de versões para mudar a versão do Spring Boot em um outro projeto que eu estava trabalhando há alguns anos atrás, e de repente minhas classes de service começaram a dar problema. Ao investigar, percebi que a assinatura de alguns métodos mudaram, e ao invés de retornar uma entidade, estavam retornando Optional.
Nesse caso em específico, eu consegui mudar o meu código para que a atualização fizesse sentido, mas imagina se o problema fosse em uma classe de dependência transitiva que temos no projeto.
Como então resolver o problema de saber se a atualização de uma dependência impacta ou não outras dependências, e consequentemente o projeto? Podemos ir na tentativa e erro, subindo nossa aplicação e vendo se todos os fluxos dela continuam funcionando. Mas será que não tem uma forma melhor?
Continuous Integration
Em posts mais adiante falaremos sobre GitLab e pipelines. Quando falarmos sobre pipelines discorreremos sobre a importância do processo de automação dado o número de alterações de um projeto por várias pessoas.
Uma forma que poderíamos fazer seria então termos a nossa fase de testes executada em nossa pipeline sempre que um merge request for realizado, por exemplo.
Podemos agora fazer mudanças em versões de uma biblioteca e ver se nossos testes não quebram. Para essa abordagem é importante que confiemos nos nossos testes e na quantidade de camadas cobertas por eles.
Quando digo em confiar em nossos testes, me refiro aos seguintes testes que muitas vezes encontramos em nossos projetos:
import io.quarkus.test.junit.QuarkusTest;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
@QuarkusTest
public class Teste {
private int adicao(int a, int b) {
return a + b;
}
@Test
void deveAdicionar() {
Assertions.assertNotNull(adicao(1, 2));
}
}
O teste com assertNotNull pode nos dar um falso positivo sobre o funcionamento do nosso método. Imagine por exemplo que um desenvolvedor subiu uma versão mudando o método para algo como:
private int adicao(int a, int b) {
return a - b;
}
Nosso teste não iria quebrar! Bem importante usar testes com assertNotNull com parcimônia.
Esse foi apenas um exemplo de teste que poderia ser melhor testado, mas imagina se fizermos testes que não quebrem, porém, quando fizermos o deploy em produção aparecerem incompatibilidades.
Uma abordagem que podemos seguir para saber se temos bons testes foi escrita por @alexandreaquiles:
Mas voltando à atualização das nossas bibliotecas, não seria maravilhoso se tivesse uma forma melhor do que nós mesmos abrirmos merges request para cada mudança?
Dependabot
O Dependabot é uma solução que funciona com Ruby, JavaScript, Python, PHP, Elixir, Elm, Go, Rust, Java e .NET.
E seu funcionamento se dá em uma constante validação de novas versões e abertura de merge requests para um repositório configurado. Não configurei nesse projeto, mas vocês podem ver a ideia dele nesse outro repositório que criei.
Com isso, é possível aprovar ou não os merge requests que o Dependabot fará para o nosso projeto.
Conclusão
A atualização de dependências de um projeto é um processo que poderá ser necessário em projetos, porém é importante avaliarmos os impactos das mesmas e consequências em outras bibliotecas do projeto.
Soluções como Gradle Versions Plugin ou Dependabot podem fazer parte do nosso processo para uma maior assertividade na alterações de tais versões, atrelados a processos como Continuous Integration.
Esse post faz parte de uma série sobre Cursos que formaram meu caráter: Desenvolvimento web com Quarkus.
A série completa é:
- Talk is cheap show me the code
- Utilizando Gradle ao invés de Maven
- Plugins do Gradle: Gerenciador de versões de bibliotecas com Versions
- Plugins do Gradle: Saudades do Maven, relatórios com Project-report
- Plugins do Gradle: API First com o OpenAPI Generator
- Plugins do Gradle: API First com o OpenAPI Generator para APIs reativas
- Plugins do Gradle: Validação de vulnerabilidades de dependências com OWASP Dependency Check
- Plugins do Gradle: Lint com Spotless [não publicado]
- GitLab: Motivação para a escolha [não publicado]
- GitLab: Considerações sobre o Prettier em pipelines [não publicado]
- Gitmoji [não publicado]
- SDK Man [não publicado]
- Pré commit hook [não publicado]
- application.yml: Utilizando YML em propriedades de aplicação [não publicado]
- application.yml: Utilizando variáveis de ambiente [não publicado]
- application.yml: Sobrescrevendo opções em teste [não publicado]
- Mapstruct: Utilizando expressões para mapeamentos [não publicado]
- Microprofile SecurityScheme: Sobrescrevendo tokenUrl [não publicado]
- Prometheus: O problema simples que me custou algumas horas [não publicado]
- quarkus-hibernate-reactive-panache: Utilizando as vantagens do Hibernate em projetos reativos [não publicado]
- Testcontainers: Configuração para projetos reativos [não publicado]
Top comments (0)