Antes dos módulos, Go usava o GOPATH — um diretório global onde todos os projetos e dependências conviviam. Não havia versionamento de dependências, não havia isolamento entre projetos e atualizar uma biblioteca podia quebrar todos os projetos simultaneamente. Era um sistema funcional para projetos pequenos e experimentos, mas inadequado para desenvolvimento profissional em escala.
Go Modules, introduzido como experimental no Go 1.11 e tornado padrão no Go 1.16, resolveu todos esses problemas. Cada projeto tem seu próprio conjunto de dependências versionadas, isolado dos demais. Builds são reproduzíveis — o mesmo código usa exatamente as mesmas versões de dependências em qualquer máquina. Atualizações são explícitas e controladas.
O arquivo go.mod
O go.mod é o manifesto do módulo. Ele declara o nome do módulo, a versão mínima do Go necessária e as dependências — as diretas e, desde o Go 1.17, também as indiretas necessárias para o build, marcadas com // indirect:
module github.com/ricardomattos/loja-api
go 1.22
require (
github.com/gin-gonic/gin v1.9.1
github.com/golang-jwt/jwt/v5 v5.2.0
gorm.io/gorm v1.25.7
gorm.io/driver/postgres v1.5.6
)
require (
// dependências indiretas — usadas por dependências diretas
github.com/bytedance/sonic v1.11.3 // indirect
github.com/gabriel-vasile/mimetype v1.4.3 // indirect
golang.org/x/net v0.22.0 // indirect
)
O caminho do módulo — github.com/ricardomattos/loja-api — é o identificador único do módulo. Por convenção, projetos publicados usam o caminho do repositório onde o código reside. Para projetos privados ou locais, qualquer nome descritivo funciona.
O arquivo go.sum
O go.sum é o guardião da integridade das dependências. Para cada módulo listado no go.mod, o go.sum armazena o hash criptográfico do conteúdo esperado:
github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg=
github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU=
Quando você ou um colega executa go build ou go test, o Go verifica se os módulos baixados correspondem aos hashes registrados. Se alguém alterar o conteúdo de uma dependência publicada — seja maliciosamente ou por acidente — a verificação falha e o build é recusado.
Nunca edite o go.sum manualmente. Ele é gerenciado automaticamente pelos comandos go. Sempre faça commit de ambos go.mod e go.sum no controle de versão.
Comandos essenciais
Inicializando um módulo:
mkdir loja-api
cd loja-api
go mod init github.com/ricardomattos/loja-api
Adicionando uma dependência:
A forma mais direta é simplesmente importar o pacote no código e executar go mod tidy. Ou adicionar explicitamente com go get:
go get github.com/gin-gonic/gin@v1.9.1
go get gorm.io/gorm@latest
O sufixo @versão aceita: - @v1.9.1 — versão exata - @latest — versão mais recente estável - @main — branch específica (evite em produção) - @v1.9 — prefixo de versão
Removendo dependências não utilizadas e adicionando as faltantes:
go mod tidy
Este é o comando mais usado no dia a dia. Ele analisa todo o código do módulo, remove do go.mod dependências que não são mais importadas, adiciona as que estão sendo importadas mas não declaradas e atualiza o go.sum. Execute go mod tidy antes de todo commit.
Atualizando dependências:
# Atualizar uma dependência específica para a versão mais recente
go get github.com/gin-gonic/gin@latest
# Atualizar as dependências dos pacotes do módulo, indiretas inclusive, para o patch mais recente
go get -u=patch ./...
# O mesmo, aceitando versões minor novas
go get -u ./...
Verificando dependências:
# Lista todas as dependências e suas versões
go list -m all
# Mostra por que uma dependência está no go.mod
go mod why github.com/gin-gonic/gin
# Verifica se o go.sum está correto
go mod verify
Versionamento semântico
Go Modules adota versionamento semântico — semver — com três componentes: MAJOR.MINOR.PATCH.
- PATCH — correções de bugs sem quebrar compatibilidade:
v1.2.3 → v1.2.4 - MINOR — novas funcionalidades sem quebrar compatibilidade:
v1.2.3 → v1.3.0 - MAJOR — mudanças incompatíveis com versões anteriores:
v1.2.3 → v2.0.0
Go trata versões MAJOR diferentes como módulos completamente distintos. Um módulo na versão v2 ou superior deve ter /v2 no final do caminho do módulo:
github.com/autor/biblioteca ← v0 e v1
github.com/autor/biblioteca/v2 ← v2
github.com/autor/biblioteca/v3 ← v3
Isso permite que um projeto importe v1 e v2 de uma mesma biblioteca simultaneamente sem conflito — cada versão major é tratada como um módulo independente.
Algoritmo de versão mínima (MVS)
O Go usa o Minimum Version Selection para resolver versões de dependências, em vez de "pegar a mais recente". Dado que o projeto A requer B@v1.2 e C@v1.0, e que C@v1.0 requer B@v1.3, o Go seleciona B@v1.3 — a versão mínima que satisfaz todos os requisitos.
Esse algoritmo tem uma propriedade valiosa: ele é determinístico e conservador. Atualizar uma dependência direta nunca atualiza automaticamente outras dependências além do necessário. Isso torna os builds previsíveis e as atualizações controláveis.
Replace e exclude
O go.mod suporta diretivas avançadas para situações especiais.
replace — substitui um módulo por outro, útil para desenvolvimento local ou para usar um fork:
replace (
// Usar versão local durante desenvolvimento
github.com/ricardomattos/pacote-compartilhado => ../pacote-compartilhado
// Usar um fork em vez do original
github.com/original/biblioteca => github.com/meufork/biblioteca v1.0.0-fork
)
Remova todas as diretivas replace de desenvolvimento antes de publicar o módulo — elas não funcionam para quem baixar sua biblioteca.
exclude — exclui uma versão específica de uma dependência, geralmente por conter um bug crítico:
exclude github.com/alguma/biblioteca v1.5.2
Workspaces: múltiplos módulos locais
Go 1.18 introduziu workspaces (go work) para facilitar o desenvolvimento simultâneo de múltiplos módulos relacionados sem precisar usar replace:
# Na raiz que contém os dois módulos
go work init ./loja-api ./pacote-compartilhado
Isso cria um arquivo go.work:
go 1.22
use (
./loja-api
./pacote-compartilhado
)
Com o workspace ativo, mudanças em pacote-compartilhado são imediatamente visíveis em loja-api sem necessidade de publicar ou usar replace. O go.work não deve ser commitado — adicione-o ao .gitignore.
Cache de módulos
Go mantém um cache local de todos os módulos baixados em $GOPATH/pkg/mod. Uma vez baixado, um módulo não é baixado novamente — mesmo em projetos diferentes que usam a mesma versão.
# Ver o diretório do cache
go env GOMODCACHE
# Limpar o cache completamente
go clean -modcache
O cache é somente-leitura após o download, o que evita modificações acidentais. Se precisar editar uma dependência, faça um fork e use replace.
Proxy de módulos
Por padrão, Go baixa módulos através do proxy oficial proxy.golang.org, que serve como cache e espelho de módulos públicos. Isso garante disponibilidade mesmo se o repositório original for removido.
A variável GOPROXY controla quais proxies são usados:
# Padrão — proxy oficial com fallback direto
GOPROXY=https://proxy.golang.org,direct
# Sem proxy — acesso direto aos repositórios
GOPROXY=direct
# Proxy corporativo para ambientes fechados
GOPROXY=https://proxy.minha-empresa.com,direct
Para módulos privados, que o proxy público não alcança e a base de checksums pública não conhece, a variável que resolve os dois lados de uma vez é GOPRIVATE: go env -w GOPRIVATE=github.com/minha-empresa/* faz o Go buscar esses módulos direto no repositório e dispensar a consulta à base de checksums. Por baixo, ela define os valores padrão de GONOPROXY e GONOSUMDB, que existem para quando os dois comportamentos precisam ser ajustados separadamente.
Exemplo completo: projeto real com dependências
mkdir api-tarefas
cd api-tarefas
go mod init github.com/ricardomattos/api-tarefas
// main.go
package main
import (
"github.com/ricardomattos/api-tarefas/internal/handler"
"github.com/gin-gonic/gin"
)
func main() {
r := gin.Default()
r.GET("/tarefas", handler.ListarTarefas)
r.POST("/tarefas", handler.CriarTarefa)
r.Run(":8080")
}
# Adicionar dependências detectadas no código
go mod tidy
Após go mod tidy, o go.mod será atualizado automaticamente com Gin e todas as suas dependências transitivas. O go.sum será gerado com os hashes correspondentes.
# Verificar o resultado
cat go.mod
# Confirmar que tudo está correto
go build ./...
go test ./...
Com módulos, as dependências de um projeto Go ficam descritas em dois arquivos de texto que entram no controle de versão: o go.mod diz quais versões usar, o go.sum garante que o conteúdo delas é o esperado. O resto decorre de escolhas conservadoras — a seleção de versão mínima, que só muda o build quando alguém pede, o caminho com /v2 para cada quebra de compatibilidade e o replace que só vale no projeto principal. No dia a dia, go mod tidy antes do commit e um diff do go.mod revisado com atenção resolvem quase tudo.
Fontes e leituras recomendadas
-
Documentação oficial de Go Modules — Referência completa e autoritativa sobre o sistema de módulos. https://go.dev/ref/mod
-
Go Blog: Using Go Modules — Série de artigos introdutórios sobre módulos. https://go.dev/blog/using-go-modules
-
Go Blog: Go Modules in 2019 — Contexto histórico e motivações do sistema de módulos. https://go.dev/blog/modules2019
-
pkg.go.dev — Repositório central de documentação de módulos Go públicos. https://pkg.go.dev
-
Documentação de go work — Referência sobre workspaces para múltiplos módulos. https://go.dev/ref/mod#workspaces
-
Go Proxy Protocol — Especificação do protocolo de proxy de módulos. https://go.dev/ref/mod#goproxy-protocol
Exercícios
Exercício 1
Um projeto exige B v1.2.0 e usa C, que exige B v1.3.0. Na semana seguinte, sai o B v1.9.0. Um desenvolvedor vindo do npm teme que o próximo go build no CI puxe a 1.9 sem ninguém pedir. Qual versão de B entra no build hoje, qual entra na semana que vem, e o que precisaria acontecer para a 1.9 aparecer?
Ver resposta
✓ Resposta: Hoje e na semana que vem entra a mesma: B v1.3.0. Pela seleção de versão mínima que o artigo descreve, o Go junta todas as exigências do grafo e escolhe, para cada módulo, a maior das mínimas declaradas: a 1.2 do projeto e a 1.3 de C resultam em 1.3. A existência de uma 1.9 no mundo não entra na conta, porque ninguém no grafo a pediu. É a diferença para o ^1.2.0 do npm, que aceita qualquer versão compatível mais nova e, sem lockfile, muda o build sozinho. No Go, o próprio go.mod já funciona como trava, e o go.sum garante que o conteúdo de cada versão é o mesmo em todas as máquinas. Para a 1.9 entrar, alguém precisa pedir de forma explícita, com go get B@v1.9.0, go get B@latest ou go get -u, e essa mudança aparece como diff no go.mod, que passa pela revisão como qualquer outro código.
Exercício 2
Uma biblioteca interna quebra a compatibilidade da API e o time cria a tag v2.0.0 sem mexer no go.mod, que continua declarando module github.com/empresa/lib. Os consumidores não conseguem atualizar. O que ficou faltando, e por que o Go exige esse passo?
Ver resposta
✓ Resposta: Faltou o sufixo de versão maior no caminho do módulo. A partir da v2, o go.mod precisa declarar module github.com/empresa/lib/v2, e os consumidores importam github.com/empresa/lib/v2/.... Sem isso, o Go recusa a tag v2 para aquele caminho, porque um módulo com go.mod só pode ter versões v0 e v1 no caminho sem sufixo. A regra vem do que o artigo chama de módulos distintos: se a v2 quebra a API, ela é tratada como outro módulo, com outro caminho, e um programa pode até ter v1 e v2 ao mesmo tempo, uma usada por uma dependência antiga e outra pelo código novo, sem conflito. O passo completo é mudar a linha module, atualizar os imports internos da própria biblioteca para o caminho com /v2 e só então criar a tag. Do lado dos consumidores, migrar é trocar os imports, e go get github.com/empresa/lib/v2@latest traz a versão nova.
Exercício 3
Uma biblioteca publicada tem no seu go.mod a linha replace github.com/outro/dep => github.com/empresa/dep-corrigida v1.0.1, com uma correção importante. Um projeto que usa a biblioteca continua recebendo o bug. Por que o replace não teve efeito, e como fazer a correção chegar aos consumidores?
Ver resposta
✓ Resposta: Diretivas replace e exclude só valem no go.mod do módulo principal, aquele em que o go build está sendo executado. Os replace das dependências são ignorados, e o artigo aponta esse efeito ao pedir que se removam os replace antes de publicar. A regra existe para que nenhuma dependência consiga desviar em silêncio o código que o seu projeto usa: só o dono do build decide substituições. Há três saídas. A melhor é levar a correção para o módulo original e, depois que ela for publicada, subir a versão mínima no require da biblioteca. Enquanto isso não acontece, cada projeto consumidor pode pôr o mesmo replace no próprio go.mod, conscientemente. A terceira é a biblioteca passar a depender do fork com o caminho do fork, o que funciona para todos mas dificulta voltar ao original depois.
Exercício 4
Um desenvolvedor usa go work para trabalhar em dois módulos ao mesmo tempo e, por engano, faz commit do go.work, que aponta para ../pacote-compartilhado. Na máquina dele tudo compila; no CI, o build quebra ou, pior, usa uma versão diferente da declarada no go.mod. O que aconteceu, e como o CI pode se proteger?
Ver resposta
✓ Resposta: Quando existe um go.work no diretório atual ou em algum acima, o comando go entra em modo workspace e usa os módulos listados em use a partir das pastas locais, no lugar das versões do require. Na máquina do desenvolvedor, ../pacote-compartilhado existe e tem as mudanças em andamento. No CI, ou a pasta não existe e o build falha, ou existe com outro conteúdo e o build passa com um código que não corresponde a nenhuma versão publicada, o que é o pior cenário, porque ninguém percebe. Por isso o artigo recomenda manter o go.work fora do controle de versão, no .gitignore. Para o CI não depender da disciplina de cada um, basta rodar os comandos com GOWORK=off, que desliga o modo workspace mesmo que o arquivo exista, e ter também um passo com go mod tidy seguido de git diff --exit-code, que falha se o go.mod ou o go.sum commitados não baterem com o código.
Exercício 5
O projeto usa o gerador stringer e o golangci-lint, e cada desenvolvedor instala as ferramentas com go install ...@latest, cada um numa versão diferente. Como fixar a versão dessas ferramentas no próprio módulo, com os recursos do Go atual?
Ver resposta
✓ Resposta: Desde o Go 1.24, o go.mod tem a diretiva tool. go get -tool golang.org/x/tools/cmd/stringer@v0.x.y acrescenta uma linha tool com o caminho do pacote e registra o módulo no require com a versão escolhida, e o go.sum ganha os hashes, como em qualquer dependência. A partir daí, go tool stringer compila e executa exatamente aquela versão, em qualquer máquina e no CI, sem instalação à parte; e go generate pode chamar go tool stringer nas diretivas //go:generate. Atualizar vira um go get -tool ...@nova-versão, revisado no diff do go.mod como qualquer outra mudança, e go get -tool caminho@none remove a ferramenta. Antes disso, o truque era um arquivo tools.go com imports em branco e uma build tag para excluí-lo do binário; ainda aparece em projetos antigos e pode ser migrado. Ferramentas com muitas dependências, como linters grandes, às vezes ficam num módulo separado, para não misturar o grafo delas com o do projeto.