Close

Como lidar com as dependências do Maven na transição para o Git

Foto profissional de Nicola Paolucci
Matt Shelton

Representante do desenvolvedor


Então, a gente está migrando para o Git e gosta do git-flow. E agora? Hora de testar tudo! Minha equipe é ótima. Eles reuniram uma lista de resultados de fluxos de trabalho de desenvolvedores no Confluence, tudo baseado no que a gente estava fazendo como equipe e em todas as coisas estranhas que eles pensaram que a gente poderia ter que fazer no futuro. Então, em uma estrutura de projeto que espelhava a que a gente tinha (mas sem código nela — apenas um pom.xml), testaram todos os fluxos de trabalho.

As dependências do Maven estavam prestes a provar que eram nosso maior problema em tudo isso.

Numeração de builds do Maven O Maven produz builds 1.0.0-SNAPSHOT até você lançar. Quando você lança, -SNAPSHOT é removido e sua versão é 1.0.0. Seu processo de build precisa ser capaz de suportar o incremento de sua versão secundária após o fato, para que o trabalho subsequente em seu próximo esforço produza builds como 1.1.0-SNAPSHOT. Você não está vinculado a três dígitos — você pode definir isso quando inicia um projeto, mas usamos três. De qualquer forma, a parte -SNAPSHOT é muito importante de entender. Ela sempre vai representar a última versão de pré-lançamento de um projeto.

Artefatos


Nossa grande preocupação em todos esses fluxos de trabalho era como garantir que as versões de projeto e dependências entre projetos fossem bem gerenciadas.

Cada vez que as dependências do Maven são recuperadas para um build, ele vai, por padrão, fazer a extração da boa e velha internet. Esses artefatos são armazenados no local para que os builds subsequentes possam ser executados mais rápido. Uma solução para tornar isso um pouco menos trabalhoso é usar um repositório de artefatos na rede local para atuar como um cache local para essas dependências externas. A recuperação de LAN quase sempre vai ser mais rápida do que fazer o download, mesmo das CDNs mais rápidas. Usamos o Artifactory Pro como nosso repositório de artefatos. Além disso, como temos uma estrutura de vários módulos, armazenamos os próprios artefatos de build no Artifactory também. Quando construímos um dos pacotes comuns, podemos extrair essa versão específica por meio da resolução de dependência do Maven e recuperar o artefato diretamente do repositório de artefatos.

Isso funciona perfeitamente. O Artifactory também permite sincronizar seus artefatos entre instâncias. Portanto, se você quiser, digamos, usá-lo para replicar seu repositório de versão para seus data centers para implementações de produção, você poderia fazer isso sem precisar criar um processo separado.

bancos de dados
Material relacionado

Como mover um Repositório do Git completo

Logotipo do Bitbucket
VER SOLUÇÃO

Aprenda a usar o Git com o Bitbucket Cloud

Dependências do Maven, ramificações de funções e pull requests


Todos os builds vão para o Artifactory. Com o SVN, estávamos usando um repositório de instantâneos para manter os dois builds de instantâneos mais recentes, um repositório de staging para todos os builds de lançamento ainda não aprovadas e um repositório de lançamento apenas para os builds que vão entrar em produção.[1] Esses builds são numerados como descrevi anteriormente e podem ser recuperados por um padrão de URL previsível com base no repositório e na versão.

O fluxo de trabalho principal para cada desenvolvedor era criar uma ramificação de função a partir da ramificação de desenvolvimento para o trabalho, concluí-lo e fazer uma pull request para que esse trabalho passasse por merge para a ramificação de desenvolvimento. Para um único projeto, isso funciona quase sem problemas, mas deixe-me descrever o primeiro problema que encontramos — o qual nos fez reconsiderar seriamente toda a migração:

Como eu disse antes, temos várias camadas de dependência entre nossos projetos. Há uma boa razão para isso, do ponto de vista histórico e estratégico, para nossos produtos. Consideramos arquiteturas alternativas que eliminariam esse problema, mas elas apresentariam outras. Podemos facilitar nossas vidas (e fizemos isso, mas esse assunto fica para outro texto), mas por enquanto é estratégico mantermos a estrutura como está.

Então, a desenvolvedora A, vamos chamá-la de Angela, começa a trabalhar em uma função no Jira. Ela vai precisar de duas ramificações: uma do nosso projeto comum e outra do produto X. A versão para o projeto comum é 2.1.0-SNAPSHOT. A versão do productX é 2.4.0-SNAPSHOT. Ela trabalha localmente por um tempo e, por fim, envia e volta para o Bitbucket Server. O Bamboo pega essas alterações, constrói o pacote comum e faz o upload do common-2.1.0-SNAPSHOT para o Artifactory e, em seguida, cria o productX com uma dependência no common-2.1.0-SNAPSHOT, carregando o productX-2.4.0-SNAPSHOT também. Os testes de unidade passam!

O desenvolvedor B, vamos chamá-lo de Bruce, começa a trabalhar em outra função no Jira, para um produto diferente: productY. Ele também vai precisar de duas ramificações: uma do nosso projeto comum e outra do productY. A versão para o projeto comum é, como acima, 2.1.0-SNAPSHOT. A versão do produto Y é 2.7.0-SNAPSHOT. Ele trabalha localmente por um tempo e, por fim, envia suas alterações para o Bitbucket Server. O Bamboo pega essas alterações, constrói o pacote comum e faz o upload do common-2.1.0-SNAPSHOT para o Artifactory e, em seguida, cria o productX com uma dependência no common-2.1.0-SNAPSHOT, carregando o productX-2.4.0-SNAPSHOT também. Os testes de unidade passam!

Angela, enquanto isso, encontra um pequeno bug no código productX e escreve um teste de unidade para validar a correção. Ela executa o teste no local e ele passa. Ela envia por push as alterações para o Bitbucket, e o Bamboo pega a alteração e compila o productX. O build é bem-sucedido, mas alguns dos testes de unidade falham. Não são os novos que ela escreveu, mas os primeiros das alterações iniciais na função. De alguma forma, o build do Bamboo encontrou uma regressão que o build local dela não encontrou? Como isso é possível?

Porque a dependência comum, a que o Bamboo puxou quando criou o productX, não era mais sua cópia. Bruce sobrescreveu o common-2.1.0-SNAPSHOT no Artifactory quando o build de função foi concluído. Não houve conflito de código-fonte; os dois desenvolvedores estavam trabalhando isoladamente em suas próprias ramificações, mas a fonte de informações para a recuperação de artefatos do Maven estava corrompida.

Título. Mesa de encontro.

Por cerca de um mês depois de descobrirmos esse problema, tentamos de tudo para resolver. Por meio do nosso GCT [2], conversamos com pessoas da equipe do Bamboo que usam o git-flow e conversamos com o desenvolvedor que mantém o git-flow, uma implementação de Java do git-flow. Todos eles foram super úteis, mas sem um processo que exigia uma lista de etapas manuais para cada desenvolvedor toda vez que eles trabalhavam em uma função, não conseguimos encontrar uma resolução que fosse tolerável.

Se você está curioso(a) sobre o que consideramos, aqui está tudo o que tentamos:

1. Modifique o número da versão na criação da ramificação ou de imediato.

  • Podemos fazer isso com mvn jgitflow:feature-start para criar a ramificação.
  • Podemos usar um hook do Bitbucket Server ou um githook local.
  • Podemos definir manualmente com mvn version:set-version depois de criarmos a ramificação.
  • Podemos automatizar a alteração com o plugin [maven-external-version].

2. Modifique o número da versão ao finalizar a ramificação e mesclar de volta para o desenvolvimento.

  • Podemos fazer isso com mvn jgitflow:feature-finish para encerrar a ramificação.
  • Use um driver git merge para lidar com conflitos de pom.
  • Use um hook assíncrono pós-recebimento no Bitbucket Server

3. Faça tudo à mão. (É brincadeira. A gente não considera essa opção por muito tempo.)

Possíveis fluxos de trabalho


Ao lembrar esse conceito central e refletir sobre ele, você pode entender que o submódulo suporta bem alguns fluxos de trabalho e outros com uma otimização menor. Existem pelo menos três cenários em que os submódulos são uma escolha justa:

  • Quando um componente ou subprojeto está mudando muito rápido ou as próximas mudanças vão quebrar a API, você pode bloquear o código para um commit específico para sua própria segurança.

  • Quando você tem um componente que não é atualizado com muita frequência e quer rastrear este componente como uma dependência do fornecedor. É o que eu faço para meus plugins vim, por exemplo.

  • Quando você está delegando uma parte do projeto a um terceiro e quer integrar o trabalho deles em um horário ou versão específico. De novo: funciona quando as atualizações não são muito frequentes.

Créditos ao finch pelos cenários bem explicados.

Cada uma dessas opções tinha algum tipo de efeito colateral negativo. Em especial etapas manuais para os desenvolvedores toda vez que eles precisavam de uma ramificação de funções. E queríamos que eles criassem ramificações de funções o tempo todo. Além disso, na maioria dos casos, não conseguimos usar efetivamente as pull requests, o que foi um problema.

Isso consumiu de uma a duas pessoas por quase dois meses até que tivemos uma revelação (alucinante) do motivo pelo qual estávamos abordando esse problema desde a perspectiva errada.

Uma versão para a todos governar


Em uma retrospectiva posterior, posso ver claramente que nosso maior erro foi que estávamos concentrando nossa atenção nas ferramentas git-flow em vez de usar as ferramentas que tínhamos para implementar o fluxo de trabalho que queríamos. Tínhamos:

  • Jira Software
  • Bamboo Server
  • Maven
  • Artifactory Pro

Acontece que essas eram todas as ferramentas que precisávamos.

Um de nossos engenheiros teve a ideia muito brilhante de que, como o problema não era o gerenciamento de build em si, mas os artefatos sendo sobrescritos, deveríamos corrigir o Artifactory. Sua ideia era usar uma propriedade Maven para definir o URL do repositório de instantâneos como uma URL personalizada que incluía o ID do item do Jira e, em seguida, gravar seus artefatos em um repositório criado dinamicamente no Artifactory com um template personalizado. O resolvedor de dependências do Maven vai encontrar artefatos no repositório de instantâneos de desenvolvimento se não precisarmos criar ramificações, por exemplo, se estivermos trabalhando apenas em um produto e não for comum.

Definimos essa pequena variável de propriedade útil no arquivo de configurações de build e escrevemos um plug-in do Maven para preenchê-lo durante a primeira parte do ciclo de vida de build do Maven. No papel, isso parecia incrível e revigorou a equipe para trabalhar ainda mais para resolver esse problema. O problema era que não conseguíamos fazer isso. O estágio inicial do ciclo de vida do Maven é “validar”. No momento em que os plug-ins vinculados à validação foram executados, as URLs do repositório já estavam resolvidas. Por causa disso, nossa variável nunca foi preenchida e a URL não tem nome de ramificação. Mesmo que estivéssemos usando um layout em um repositório separado dos instantâneos de desenvolvimento, ele não seria isolado para desenvolvimento paralelo.

E lá vamos nós DE NOVO.

Depois de uma cerveja, o engenheiro mencionado fez mais algumas pesquisas sobre outra maneira de adicionar funcionalidade ao Maven: extensões.

"Um brinde à cerveja: a causa e a solução para todos os problemas da vida". - Homer Simpson

Extensões, como plug-ins, oferecem uma grande quantidade de poder para aprimorar seu fluxo de trabalho do Maven, no entanto, elas são executadas antes das metas do ciclo de vida e têm maior acesso aos componentes internos do Maven. Ao utilizar o pacote RepositoryUtils, forçamos o Maven a reavaliar as URLs usando um analisador personalizado e, em seguida, redefini-los usando os valores atualizados.[3]

Com a extensão implementada e testada, começamos a eliminar as tarefas pré-migração uma após a outra, passando de "isso nunca vai acontecer" para "isso VAI acontecer na segunda-feira... então agora preciso escrever dez páginas de documentação até amanhã". Em breve, vou escrever mais sobre como as ferramentas funcionam juntas para alcançar o novo fluxo de trabalho de desenvolvimento e algumas das lições que aprendemos sobre o processo.

[1]: Uma desvantagem aqui foi que eu tive que usar um script que escrevi para acessar a API REST do Artifactory para "promover" os builds desde o staging até o lançamento. É rápido o suficiente, mas pede por mais automação.

[2]: Gerente de contas técnicas. Mais informações aqui.

[3]: Após os esforços iniciais de desenvolvimento, descobrimos que tínhamos que fazer ainda mais para fazer isso funcionar 100% do tempo, como quando um instantâneo é mais novo no Artifactory (de outro engenheiro) do que o instantâneo local, o Maven pega o artefato remoto, porque é MAIS NOVO, então deve ser MELHOR, certo?


Footnotes

[1]: One downside here was that I had to use a script I wrote to hit the Artifactory REST API to "promote" builds from staging to release. It's fast enough, but begging for more automation.

[2]: Technical Account Manager. More information here.

[3]: After the initial development efforts, we found that we had to do even more to make this work 100% of the time, like when a snapshot is newer in Artifactory (from another engineer) than your local snapshot, Maven grabs the remote artifact because hey, it's NEWER, so it must be BETTER, right?

Matt Shelton
Matt Shelton

Matt Shelton é um profissional de DevOps que divulga essa prática e supervisiona a entrega de ofertas de serviços de DevOps (e outras relacionadas) da Atlassian nas Américas. Ele quase não publica mais no blog e trabalha no desenvolvimento da próxima geração de divulgadores dessa prática, que permite trabalhar de uma forma melhor.


Compartilhar este artigo

Leitura recomendada

Marque esses recursos para aprender sobre os tipos de equipes de DevOps ou para obter atualizações contínuas sobre DevOps na Atlassian.

Pessoas colaborando usando uma parede cheia de ferramentas

Blog do Bitbucket

Ilustração do DevOps

Caminho de aprendizagem de DevOps

Demonstrações de funções no Demo Den com parceiros da Atlassian

Como o Bitbucket Cloud funciona com o Atlassian Open DevOps

Inscreva-se para receber a newsletter de DevOps

Thank you for signing up