Go modules: gerenciamento de dependências e versionamento
1. Visão geral
Desde Go 1.11, Go modules é o mecanismo oficial para gerenciar dependências. A partir de Go 1.16, é o modo padrão, substituindo completamente a abordagem antiga baseada em GOPATH. Este artigo percorre o ciclo de vida completo de um módulo: inicialização, adição de dependências, seleção e atualização de versões, tratamento de major versions v2+, e trabalho com código local ou repositórios privados. Após ler, você entenderá o que go.mod e go.sum contêm. Você também saberá por que Go escolhe uma versão específica e como corrigir os erros comuns de dependências.
2. Inicializando um módulo
Um módulo é uma árvore de diretórios com um arquivo go.mod na raiz. Crie um novo módulo com go mod init:
mkdir order-service && cd order-service
go mod init github.com/nonla/order-service
O arquivo go.mod é criado:
module github.com/nonla/order-service
go 1.24
O significado de cada linha:
moduleé o module path. Este é o prefixo de import para todos os pacotes dentro do módulo. Se o módulo for importado por outras pessoas comgo get, este caminho deve corresponder à URL do repositório.go 1.24é a versão mínima da linguagem que o módulo requer. A partir de Go 1.21, esta linha é obrigatória. Se sua toolchain for mais antiga, Go carregará automaticamente a toolchain apropriada (quandoGOTOOLCHAIN=auto) ou relatará um erro.
Nota: Para aplicações internas que nunca serão importadas de fora, você pode usar um caminho curto como
order-service. No entanto, usar um caminho em forma de URL desde o início evitará ter que renomear muitos imports depois.
3. Adicionando dependências
3.1. Usando go get
Suponha que você precisa gerar IDs para pedidos usando UUID. Adicione a biblioteca github.com/google/uuid:
go get github.com/google/uuid@v1.6.0
Em seguida, escreva o código que usa a biblioteca:
// order/order.go
package order
import "github.com/google/uuid"
type Order struct {
ID string
Customer string
Total int64 // unidade: đồng
}
func NewOrder(customer string, total int64) Order {
return Order{
ID: uuid.NewString(),
Customer: customer,
Total: total,
}
}
Agora go.mod tem um bloco adicional require:
module github.com/nonla/order-service
go 1.24
require github.com/google/uuid v1.6.0
Você também pode pular a etapa go get: escreva o import primeiro, depois execute go mod tidy. Go encontrará automaticamente a versão mais recente e a gravará em go.mod.
3.2. O arquivo go.sum
Ao mesmo tempo, Go cria o arquivo go.sum:
github.com/google/uuid v1.6.0 h1:<hash nội dung module>
github.com/google/uuid v1.6.0/go.mod h1:<hash của file go.mod>
Cada dependência tem duas linhas. A primeira é o hash de todo o conteúdo do módulo, a segunda é o hash do arquivo go.mod. Ao fazer download de um módulo, Go compara o hash real com go.sum. Também verifica com o banco de dados de checksum público (sum.golang.org). Se alguém modificar o conteúdo de uma tag já lançada, o build falhará em vez de silenciosamente usar código incorreto.
Nota: Sempre faça commit de
go.modego.sum. Não adicionego.sumao.gitignore. Se este arquivo estiver faltando, a máquina CI não conseguirá verificar as dependências.
3.3. Dependências diretas e indiretas
Adicione testify para escrever testes:
go get github.com/stretchr/testify@v1.10.0
go.mod agora terá dois blocos require:
require (
github.com/google/uuid v1.6.0
github.com/stretchr/testify v1.10.0
)
require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
O comentário // indirect marca um módulo que seu código não importa diretamente, mas que é necessário para outra dependência. Go os grava em go.mod para que o build seja totalmente reproduzível. Assim, Go não precisa ler o go.mod de cada dependência novamente para construir o gráfico.
4. Versioning e como Go escolhe versões
4.1. Semantic versioning
Go modules usa semantic versioning no formato vMAJOR.MINOR.PATCH:
PATCH(v1.6.0 → v1.6.1): correções de bugs, sem alterações de API.MINOR(v1.6.0 → v1.7.0): adiciona recursos, mantém compatibilidade reversa.MAJOR(v1 → v2): mudanças que quebram compatibilidade.
As versões devem ser git tags com o prefixo v. Se você apontar para um commit sem tag, Go cria uma pseudo-versão:
go get github.com/google/uuid@0f3b7a2
github.com/google/uuid v1.6.1-0.20240815101530-0f3b7a2c1d4e
A pseudo-versão tem três partes: versão base, timestamp UTC do commit e os 12 primeiros caracteres do hash. Assim, a pseudo-versão ainda ordena corretamente pela hora.
4.2. Seleção de versão mínima
Esta é a maior diferença em relação ao npm ou Maven. Go usa o algoritmo Minimal Version Selection (MVS). Para cada módulo, Go escolhe a versão mais alta entre aquelas que são necessárias, não a versão mais recente que existe.
Por exemplo, seu projeto tem duas dependências:
order-service
├── payment-sdk → require github.com/google/uuid v1.3.0
└── shipping-sdk → require github.com/google/uuid v1.5.0
Mesmo que uuid v1.6.0 tenha sido lançado, Go ainda escolhe v1.5.0. Razão: essa é a versão mais baixa que satisfaz todos os requisitos. Consequências:
- O build é reproduzível. Uma biblioteca lançar uma nova versão não muda automaticamente seu build.
- Não é necessário um arquivo de lock separado.
go.modé suficiente para determinar as versões. - Para usar uma versão mais recente, você deve fazer upgrade de forma proativa.
Veja quais versões foram realmente escolhidas com o comando:
go list -m all
github.com/nonla/order-service
github.com/davecgh/go-spew v1.1.1
github.com/google/uuid v1.5.0
...
4.3. Upgrading e downgrading
Verifique quais dependências têm novas versões:
go list -m -u all
github.com/google/uuid v1.5.0 [v1.6.0]
github.com/stretchr/testify v1.10.0
A versão entre colchetes é a versão mais recente que você pode fazer upgrade. Os comandos de upgrade mais comuns são:
go get github.com/google/uuid@latest # versão mais recente
go get github.com/google/uuid@v1.5.0 # versão específica (pode fazer downgrade)
go get -u=patch ./... # apenas PATCH para todas as dependências
go get -u ./... # MINOR + PATCH para todas as dependências
go get github.com/google/uuid@none # remove a dependência
Nota:
go get -u ./...atualiza todas as dependências, incluindo as indiretas. O diff pode ser muito grande. Em projetos de produção, você deve atualizar cada módulo, executar testes e só então fazer commit.
5. Major version v2 e acima
Go aplica a regra de import compatibility: duas versões com o mesmo import path devem ser compatíveis para trás. Portanto, a partir de v2, a major version faz parte do module path.
Por exemplo, com o router chi:
go get github.com/go-chi/chi/v5@v5.1.0
package main
import (
"net/http"
"github.com/go-chi/chi/v5"
)
func main() {
r := chi.NewRouter()
r.Get("/orders", func(w http.ResponseWriter, _ *http.Request) {
w.Write([]byte("lista de pedidos"))
})
http.ListenAndServe(":8080", r)
}
O sufixo /v5 está no import path. O nome do pacote ainda é chi, então você chama chi.NewRouter() normalmente. Já que chi/v4 e chi/v5 têm paths diferentes, ambas as versões podem coexistir no mesmo binário. Isso permite que você migre gradualmente.
Se você é o autor de uma biblioteca e deseja lançar v2:
module github.com/nonla/pricing/v2
go 1.24
Você muda a linha module para um path com o sufixo /v2, atualiza os imports internos e marca a tag v2.0.0.
Nota: Um erro comum é apenas marcar a tag
v2.0.0e esquecer de alterar o module path. Nesse caso,go get github.com/nonla/pricing@v2.0.0relatará um erro comoinvalid version: module contains a go.mod file, so module path must match major version.
6. Comandos úteis de go mod
go mod tidy # adiciona módulos faltando, remove módulos não utilizados
go mod why -m gopkg.in/yaml.v3 # explica por que o módulo está presente
go mod graph # imprime o gráfico de dependências completo
go mod verify # verifica se cache local corresponde com go.sum
go mod download # faz download das dependências antecipadamente (útil para camadas Docker)
Exemplo de saída de go mod why:
# gopkg.in/yaml.v3
github.com/nonla/order-service/order
github.com/stretchr/testify/assert
gopkg.in/yaml.v3
Leia de cima para baixo: o pacote order (no teste) importa testify/assert, e testify/assert por sua vez importa yaml.v3.
Execute go mod tidy antes de cada commit. No CI, você pode verificar se go.mod já está limpo:
go mod tidy -diff
A partir de Go 1.23, a flag -diff imprime a diferença e retorna um código de saída diferente de 0 se o arquivo ainda não está tidy. Este comando não sobrescreve o arquivo.
7. Trabalhando com código local: replace e go.work
7.1. A diretiva replace
Suponha que você está corrigindo a biblioteca pricing em um diretório adjacente e quer que order-service use a versão que está sendo corrigida:
require github.com/nonla/pricing v1.2.0
replace github.com/nonla/pricing => ../pricing
Go lerá o código de ../pricing em vez de fazer download da rede. replace também pode apontar para um fork:
replace github.com/nonla/pricing => github.com/minh/pricing v1.2.1-fix
Nota:
replacesó funciona no módulo principal. Se seu módulo é uma biblioteca, os usuários não receberão oreplace. Além disso, não faça commit dereplaceapontando para caminhos locais. O build no CI falhará porque o diretório../pricingnão existirá lá.
7.2. Workspace com go.work
A partir de Go 1.18, workspace é uma forma mais limpa de desenvolver múltiplos módulos simultaneamente:
go work init ./order-service ./pricing
O arquivo go.work é criado:
go 1.24
use (
./order-service
./pricing
)
Ao executar go build ou go test no workspace, qualquer import de github.com/nonla/pricing usará o código local. Você não precisa alterar go.mod. Normalmente go.work não é feito commit; cada desenvolvedor cria seu próprio baseado em sua organização de diretórios.
7.3. exclude e retract
exclude é usado no módulo principal para descartar uma versão específica:
exclude github.com/google/uuid v1.4.0
retract é declarado pelo autor da biblioteca para indicar que uma versão já lançada não deve ser usada:
retract v1.2.0 // cálculo incorreto de imposto VAT para pedidos internacionais
Depois de você lançar uma nova versão contendo esta linha, go get @latest ignorará v1.2.0. O comando go list -m -u também alertará qualquer pessoa que esteja usando essa versão.
8. Módulos privados e proxy
Por padrão, Go faz download de módulos via GOPROXY=https://proxy.golang.org,direct e verifica checksums via sum.golang.org. Para repositórios internos, o proxy público não pode acessar, e você também não quer expor os nomes dos módulos. Declare:
go env -w GOPRIVATE=github.com/nonla/*,gitlab.nonla.internal/*
Módulos que correspondem a padrões em GOPRIVATE serão baixados diretamente via git e ignorarão o banco de dados de checksums. Você ainda precisa configurar autenticação para git, por exemplo via ~/.netrc ou SSH:
git config --global url."git@github.com:".insteadOf "https://github.com/"
Se sua empresa tem um proxy privado (Athens, Artifactory…), coloque esse proxy no início de GOPROXY:
go env -w GOPROXY=https://goproxy.nonla.internal,https://proxy.golang.org,direct
9. Testando comportamento com dependências
Escreva testes para NewOrder usando testify adicionado na seção 3.3:
// order/order_test.go
package order
import (
"testing"
"github.com/google/uuid"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestNewOrder_GeneratesValidUniqueID(t *testing.T) {
first := NewOrder("Nguyễn Văn An", 250_000)
second := NewOrder("Trần Thị Bình", 120_000)
_, err := uuid.Parse(first.ID)
require.NoError(t, err)
assert.NotEqual(t, first.ID, second.ID)
assert.Equal(t, "Nguyễn Văn An", first.Customer)
assert.EqualValues(t, 250_000, first.Total)
}
Execute os testes:
go test ./...
ok github.com/nonla/order-service/order 0.004s
O teste verifica três coisas: o ID é um UUID válido, os dois pedidos têm IDs diferentes e os outros campos foram atribuídos corretamente. require.NoError interrompe o teste imediatamente se o ID não for analisável. assert registra o erro e continua executando. Na primeira execução em uma máquina nova, Go baixará a dependência com a versão exata em go.mod e verificará o hash com go.sum antes de compilar.
10. Erros comuns
missing go.sum entry for module providing package:go.sumestá faltando a linha correspondente, geralmente porque alguém editougo.modmanualmente. Solução: executego mod tidy.checksum mismatch: o conteúdo do módulo não corresponde ao hash. Tentego clean -modcache. Se ainda falhar com um módulo público, considere como um sinal sério e não remova a linha dego.sumpara contornar.go: updates to go.mod needed: CI geralmente é executado com-mod=readonly, enquantogo.modainda não foi tidy. Executego mod tidylocalmente e faça commit novamente.- Importar a major version errada: usar
github.com/go-chi/chiem vez de.../chi/v5. Você receberá uma versão v1 antiga ou uma pseudo-versão estranha. Verifique o import path primeiro. - Abuso de
vendor/:go mod vendorainda é útil quando você precisa fazer um build completamente offline. Caso contrário, o proxy combinado comgo.sumjá garante reprodutibilidade, portanto você não precisa fazer commit de milhares de arquivos vendor.
11. Conclusão
Go modules resolve o problema de dependências com algumas ideias simples. go.mod declara requisitos, go.sum garante integridade, e Minimal Version Selection torna o build reproduzível sem a necessidade de um arquivo de lock. A major version está diretamente no import path, portanto mudanças que quebram compatibilidade sempre aparecem claramente no código.
Para o trabalho diário, você só precisa lembrar de quatro hábitos:
- Use
go get module@versionpara adicionar ou atualizar dependências intencionalmente. - Execute
go mod tidyantes de fazer commit. - Use
go.workem vez dereplaceao desenvolver múltiplos módulos simultaneamente. - Configure
GOPRIVATEpara repositórios internos.