Planejar dependências de build para seu pipeline

Concluído

Nesta unidade, você saberá mais sobre o empacotamento de código para torná-lo mais fácil de compartilhar. Você descobrirá por que deve criar pacotes, os tipos de pacotes você pode criar, em que local pode hospedar os pacotes e como acessá-los quando eles estiverem hospedados. Você também aprenderá sobre o controle de versão do pacote.

As bases de código estão cada vez maiores e mais complexas. É incomum que uma equipe escreva todo o código que o aplicativo usa. Em vez disso, a equipe inclui o código existente escrito por outros desenvolvedores. É possível que haja muitos desses pacotes ou dependências em um app. É importante gerenciar ativamente essas dependências para realizar a manutenção correta e garantir que elas cumpram os requisitos de segurança.

Vamos ver como a equipe está se saindo. Paulo reuniu a equipe para falar sobre uma alteração em potencial ao código que ajudaria outra equipe.

Reunião da equipe

Paulo: Olá, pessoal. Eu estava conversando com a equipe que está trabalhando no sistema de back-end para o Space Game. A equipe poderia usar os modelos que usamos para o site em um aplicativo de back-end que ela planeja escrever.

Marina: O que você quer dizer com modelos?

Paulo: Como você sabe, o site Space Game é um aplicativo do ASP.NET Core. Ele usa o padrão MVC (ou Model-View-Controller) para separar dados de como esses dados são exibidos na interface do usuário. Eu estava pensando que poderíamos criar um pacote contendo nossas classes de modelo para que qualquer aplicativo possa usá-las.

Marina: Qual é, exatamente, a meta?

Paulo: Ambas as nossas equipes compartilharão o mesmo banco de dados. O jogo envia ao banco de dados pontuações altas; podemos ler essas pontuações para exibi-las no placar de líderes.

Marina: Faz sentido. Como criaremos esse pacote?

Paulo: É por isso que eu queria falar com você. Temos algumas opções e estou procurando ideias.

Pedro: Eu adoraria ajudar, mas primeiro, tenho algumas perguntas. Sou novo nisso e quero entender como tudo funciona.

O que é um pacote?

Um pacote contém código reutilizável que outros desenvolvedores podem usar em seus próprios projetos, embora não tenham escrito esse código.

Para linguagens compiladas, um pacote costuma conter código binário compilado, como arquivos .dll no .NET ou arquivos .class em Java. Para linguagens que são interpretadas, em vez de compiladas, como JavaScript ou Python, um pacote pode incluir código-fonte.

De qualquer forma, pacotes normalmente são compactados em ZIP ou em um formato semelhante. Sistemas de pacote geralmente definem uma extensão de arquivo exclusivo, como .nupkg ou .jar, para tornar claro o uso do pacote. A compactação pode ajudar a reduzir o tempo de download e também produz um arquivo unificado para simplificar o gerenciamento.

Pacotes também costumam conter um ou mais arquivos que fornecem metadados ou informações sobre o pacote. Esses metadados podem descrever o que o pacote faz, especificar seus termos de licença, informações de contato do autor e versão do pacote.

Por que devo compilar um pacote?

Há vantagens em compilar um pacote, em vez de duplicar o código.

Um motivo para compilar um pacote, em vez de duplicar o código, é evitar desvio. Quando o código está duplicado, cada cópia pode rapidamente divergir para cumprir os requisitos de um aplicativo específico. Torna-se difícil migrar as alterações de uma cópia para as outras. Em outras palavras, você perde a capacidade de melhorar o código de maneiras que beneficiem a todos.

Pacotes também agrupam funcionalidades relacionadas em um componente reutilizável. Dependendo da linguagem de programação, um pacote pode fornecer aos aplicativos acesso a determinados tipos e funções enquanto restringem o acesso aos respectivos detalhes de implementação.

Outro motivo para compilar um pacote é fornecer uma maneira consistente de compilar e testar a funcionalidade do pacote. Quando o código é duplicado, cada aplicativo pode criar e testar esse código de maneiras diferentes. Um conjunto de testes pode incluir verificações de que outro conjunto poderia se beneficiar.

Uma desvantagem é que você tem outra base de código para testar e manter com um pacote. Você também deve ter cuidado ao adicionar recursos. Em termos gerais, um pacote deve conter recursos que beneficiam de muitos tipos de aplicativos. Por exemplo, Json.NET é um pacote do NuGet popular para .NET que permite trabalhar com arquivos JSON. Json.NET é um software livre, assim, a comunidade pode propor melhorias e relatar problemas.

Quando vários aplicativos podem se beneficiar do mesmo código, as vantagens superam muito as desvantagens. Você tem apenas uma base de código, apenas um conjunto de testes e apenas um processo de build para gerenciar.

Como identificar dependências?

Se a meta for reorganizar seu código em componentes separados, você precisará identificar as partes do aplicativo que podem ser removidas, empacotadas para serem reutilizadas, armazenadas em um local central e passarem por controle de versão. Talvez você queira, inclusive, substituir seu código por componentes de terceiros de software livre ou licenciados.

Há muitas maneiras de identificar as possíveis dependências na base de código. Isso inclui examinar o código em busca de padrões de reutilização, bem como a análise da arquitetura da sua solução. Aqui estão algumas formas de identificar dependências:

  • Código duplicado.

    Se determinadas partes de código aparecerem em vários lugares, isso será um bom indício de que você pode reutilizar o código. Centralize essas partes duplicadas de código e reempacote-as adequadamente.

  • Alta coesão e baixo acoplamento.

    Uma segunda abordagem é procurar elementos de código que tenham uma grande coesão entre si e um baixo acoplamento com outras partes do código. Em essência, a alta coesão significa manter partes de uma base de código que estão relacionadas entre si em um só lugar. Ao mesmo tempo, o foco do acoplamento baixo é a separar o máximo possível partes não relacionadas da base de código.

  • Ciclo de vida individual.

    Procure partes do código que tenham um ciclo de vida semelhante e que você possa implantar e liberar individualmente. Se a manutenção desse código puder ser feita por uma equipe separada, isso é um bom indício de que você pode empacotá-lo como um componente fora da solução.

  • Partes estáveis.

    Algumas partes da sua base de código podem ser estáveis e mudar com pouca frequência. Verifique seu repositório de códigos para localizar o código com baixa frequência de alteração.

  • Código e componentes independentes.

    Sempre que o código e os componentes são independentes e não relacionados a outras partes do sistema, você pode isolá-los em dependências separadas.

Você pode usar várias ferramentas para ajudar a verificar e examinar sua base de código. Essas ferramentas vão desde aquelas que verificam o código duplicado e desenham grafos de dependência de solução até ferramentas que podem computar métricas para acoplamento e coesão.

Que tipos de pacotes existem?

Cada linguagem de programação ou estrutura fornece sua própria maneira de criar pacotes. Sistemas de pacotes populares fornecem a documentação sobre como o processo funciona.

Talvez você já esteja familiarizado com estes sistemas de pacote populares:

  • NuGet: empacota bibliotecas do .NET
  • NPM: empacota bibliotecas do JavaScript
  • Maven: empacota bibliotecas do Java
  • Docker: empacota o software em unidades isoladas chamadas contêineres

Em que local os pacotes são hospedados?

Você pode hospedar pacotes na própria rede ou usar um serviço de hospedagem. Um serviço de hospedagem costuma ser chamado de repositório de pacotes ou registro de pacote. Muitos desses serviços fornecem hospedagem gratuita para projetos de software livre.

Aqui estão alguns serviços populares de hospedagem para os tipos de pacote que acabamos de descrever:

  • Galeria do NuGet

    Os pacotes NuGet são usados para artefatos de código .NET. Esses artefatos incluem assemblies .NET e arquivos relacionados, ferramentas e, às vezes, metadados. O NuGet define a maneira como os pacotes são criados, armazenados e consumidos. Um pacote NuGet é essencialmente uma estrutura de pastas compactada com arquivos no formato ZIP e tem a extensão .nupkg.

  • NPM

    Um pacote NPM é usado para JavaScript. Um pacote NPM é um arquivo ou pasta que contém arquivos JavaScript e um arquivo package.json que descreve os metadados do pacote. Para o Node.js, o pacote geralmente contém um ou mais módulos que podem ser carregados depois que o pacote é consumido.

  • Maven Central Repository

    O Maven é usado para projetos baseados em Java. Cada pacote tem um arquivo de modelo de objeto do projeto que descreve os metadados do projeto e é a unidade básica para definir um pacote e trabalhar com ele.

  • Docker Hub

    Os pacotes do Docker são chamados de imagens e contêm implantações completas e independentes. Normalmente, uma imagem do Docker representa um componente de software que pode ser hospedado e executado sozinho, sem nenhuma dependência em outras imagens. As imagens do Docker são impostas e podem depender de outras imagens.

Um feed do pacote refere-se ao servidor de repositório de pacote. Esse servidor pode estar na Internet ou protegido por um firewall em sua rede. Por exemplo, você pode hospedar seus feeds do NuGet usando produtos de hospedagem como o Azure Artifacts e o MyGet. Você também pode hospedar pacotes em um compartilhamento de arquivo.

Quando você hospeda os pacotes protegidos por um firewall, pode incluir feeds para seus próprios pacotes. Você também pode armazenar em cache os pacotes em que na sua rede quando os sistemas não podem se conectar à Internet.

Quais elementos compõem uma boa estratégia de gerenciamento de dependência?

Uma boa estratégia de gerenciamento de dependências conta com estes três elementos:

  • Padronização.

    Padronizar a forma como você declara e resolve dependências ajudará o processo de lançamento automatizado a permanecer repetível e previsível.

  • Formatos e fontes de empacotamento.

    Cada dependência deve ser empacotada usando o formato aplicável e armazenada em uma localização central.

  • Controle de versão.

    Você precisa controlar as alterações que ocorrem ao longo do tempo em dependências, assim como faria com o próprio código. Isso significa que as dependências devem ter controle de versão.

Quem pode acessar os pacotes?

Vários feeds de pacote fornecem acesso irrestrito aos pacotes. Por exemplo, você pode baixar Json.NET do nuget.org sem precisar entrar nem se autenticar.

Outros feeds de pacote exigem autenticação. Você pode autenticar o acesso a feeds de várias maneiras. Por exemplo, alguns tipos de feeds exigem um nome de usuário e senha. Outros feeds exigem um token de acesso, que normalmente é uma série longa de caracteres que identifica quem é você e a quais recursos você tem acesso. Você pode definir que os tokens de acesso expirem após um determinado período.

Como é feito o controle de versão dos pacotes?

O esquema de controle de versão depende do sistema de empacotamento usado.

Por exemplo, pacotes NuGet usam Controle de Versão Semântico.

O Controle de Versão Semântico é um esquema de controle de versão popular. Este é o formato:

Major.Minor.Patch[-Suffix]

Aqui está o significado de cada um desses parâmetros:

  • Uma nova versão Principal introduz mudanças significativas. Normalmente, os aplicativos precisam atualizar como eles usam o pacote para trabalhar com uma nova versão principal.
  • Uma nova versão principal Secundária introduz recursos, mas é compatível com versões anteriores.
  • Um novo Patch apresenta correções de bugs compatíveis com versões anteriores, mas não recursos novos.
  • A parte -Sufixo é opcional e identifica o pacote como uma versão de pré-lançamento. Por exemplo, 1.0.0-beta1 pode identificar o pacote como o primeiro build de pré-lançamento beta da versão 1.0.0.

É possível fazer referência a um pacote pelo número de versão.

Aqui está um exemplo de instalação de um pacote usando o PowerShell e um número de versão específico:

Install-Package Newtonsoft.Json -Version 13.0.1

O que acontece quando o pacote é alterado?

Quando você faz referência a um pacote do seu aplicativo, normalmente fixa (ou especifica) a versão do pacote que deseja usar.

Muitas estruturas possibilitam especificar intervalos permitidos de versões de pacote a serem instaladas. Alguns também permitem especificar caracteres curinga, o que chamamos de uma versão flutuante.

Por exemplo, no NuGet, versão "1.0" significa que a primeira versão é igual ou maior que 1.0. "[1.0]" especifica instalar a versão 1.0 apenas, não uma versão mais recente.

Aqui estão alguns outros exemplos:

Esta notação: Seleciona:
(1.0,) A primeira versão maior que 1.
[1.0,2.0] A primeira versão superior ou igual a 1.0 e inferior ou igual a 2.0
(1.0,2.0) A primeira versão maior que 1.0 e menor que 2.0
[1.0,2.0) A primeira versão superior ou igual a 1.0 e inferior a 2.0

Conforme cada mantenedor lança uma versão do pacote, você pode avaliar o que mudou e testar seu aplicativo em relação a ela. Quando você estiver pronto, poderá atualizar o número de versão do pacote em sua configuração e enviar a alteração ao seu pipeline de build.

Aqui está um exemplo de como incluir o pacote Newtonsoft.Jsno no arquivo de projeto (.csproj) do aplicativo C#. Este exemplo especifica a versão 13.0.1 do pacote:

<ItemGroup>
  <PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
</ItemGroup>

Verificar seu conhecimento

1.

O que é um pacote?

2.

Digamos que você tenha criado um pacote que queira compartilhar publicamente. Qual é a maneira mais fácil de fazer isso?