Go modules: versionamento semântico e replace

1. Fundamentos do Versionamento Semântico em Go Modules

1.1 O formato vMAJOR.MINOR.PATCH e sua aplicação no ecossistema Go

O versionamento semântico (SemVer) é a espinha dorsal do sistema de módulos do Go. Cada versão de módulo segue o formato vMAJOR.MINOR.PATCH, onde:

  • MAJOR: incrementado quando há mudanças incompatíveis na API
  • MINOR: incrementado quando novas funcionalidades são adicionadas de forma compatível
  • PATCH: incrementado para correções de bugs compatíveis
// Exemplo de declaração de versão no go.mod
module github.com/meuprojeto/calculadora

go 1.21

require github.com/sirupsen/logrus v1.9.3

1.2 Regras de compatibilidade

O Go é rigoroso quanto à compatibilidade semântica. Se você possui uma função Soma(a, b int) int e altera sua assinatura para Soma(a, b, c int) int, isso exige um incremento major. Adicionar uma nova função Multiplica sem alterar as existentes exige apenas um incremento minor.

// v1.0.0 - versão inicial
package calculadora

func Soma(a, b int) int {
    return a + b
}

// v1.1.0 - adição compatível
func Subtracao(a, b int) int {
    return a - b
}

// v2.0.0 - quebra de compatibilidade
func Soma(a, b, c int) int { // Assinatura alterada!
    return a + b + c
}

1.3 A tag go no go.mod

A diretiva go no go.mod indica a versão mínima do compilador Go necessária para compilar o módulo, não a versão do módulo em si.

module github.com/exemplo/meumodulo

go 1.21  // Requer Go 1.21 ou superior

2. Declarando e Gerenciando Dependências com Versões

2.1 Sintaxe require

A diretiva require especifica dependências e suas versões:

require (
    github.com/gorilla/mux v1.8.1
    github.com/lib/pq v1.10.9
    golang.org/x/sync v0.5.0
)

2.2 Comandos go get

# Adicionar ou atualizar dependência
go get github.com/gorilla/mux@v1.8.1

# Atualizar para última versão minor/patch
go get -u github.com/gorilla/mux

# Remover dependência
go get github.com/gorilla/mux@none

2.3 Arquivo go.sum

O go.sum contém hashes criptográficos que garantem a integridade das dependências:

github.com/gorilla/mux v1.8.1 h1:TuMoUvkRETdXqU+3a8mHkR0g7A6J30IJb2qA0...
github.com/gorilla/mux v1.8.1/go.mod h1:1lud6UwP+6orDFRuTfBEV8...

3. Versões Major e Módulos com Caminhos Diferentes

3.1 A regra de importação para v2+

Para versões major 2 ou superiores, o Go exige que o caminho do módulo inclua a versão major:

// Estrutura de diretórios
meuprojeto/
├── go.mod (module github.com/exemplo/meuprojeto/v2)
├── calculadora.go
└── main.go
// go.mod
module github.com/exemplo/meuprojeto/v2

go 1.21
// main.go
package main

import (
    "fmt"
    "github.com/exemplo/meuprojeto/v2/calculadora"
)

func main() {
    fmt.Println(calculadora.Soma(1, 2, 3))
}

3.2 Criando uma nova versão major

# Criar branch para v2
git checkout -b v2

# Alterar go.mod
module github.com/exemplo/meuprojeto/v2

# Taggear
git tag v2.0.0
git push origin v2.0.0

3.3 Convivência de múltiplas versões major

import (
    "github.com/exemplo/meuprojeto/v1/calculadora"  // v1
    v2calc "github.com/exemplo/meuprojeto/v2/calculadora"  // v2
)

func main() {
    fmt.Println(calculadora.Soma(1, 2))     // v1 - 2 parâmetros
    fmt.Println(v2calc.Soma(1, 2, 3))       // v2 - 3 parâmetros
}

4. Versões Pré-lançamento e Pseudoversões

4.1 Identificadores de pré-lançamento

// Exemplos de versões pré-lançamento
v1.0.0-alpha
v1.0.0-beta.1
v1.0.0-rc.1

4.2 Pseudoversões

Pseudoversões são geradas automaticamente para commits sem tag:

v1.0.0-20240101120000-abc123def456
# Usar pseudoversão
go get github.com/exemplo/meumodulo@abc123def456

4.3 Limitações

Pseudoversões não devem ser usadas em produção, pois não seguem SemVer e podem causar builds não reproduzíveis.

5. A Diretiva replace no go.mod

5.1 Sintaxe e casos de uso

module github.com/meuprojeto/api

go 1.21

require github.com/meuprojeto/utils v1.0.0

replace github.com/meuprojeto/utils => ../utils-local

5.2 Cenários práticos

Desenvolvimento local de dependências:

replace github.com/meuprojeto/database => /home/user/projetos/database

Patches temporários com fork:

replace github.com/gorilla/mux => github.com/meuuser/mux v1.8.2-patched

5.3 Impactos no build

replace só funciona no módulo raiz. Módulos que dependem do seu módulo não herdam diretivas replace.

// Isto NÃO funciona em dependências transitivas
// Apenas no módulo raiz
replace example.com/foo => example.com/foo v1.0.1

6. Boas Práticas com replace e Versionamento

6.1 Caminhos relativos

replace github.com/meuprojeto/utils => ./utils-local

6.2 replace vs go.work

go.work (Go 1.18+) é mais adequado para múltiplos módulos:

// go.work
go 1.21

use (
    ./api
    ./utils
    ./database
)

6.3 Removendo replace antes do commit

# Script para verificar replace antes do commit
#!/bin/bash
if grep -q "replace" go.mod; then
    echo "ERRO: go.mod contém diretivas replace!"
    exit 1
fi

7. Ferramentas e Comandos Essenciais

7.1 go mod tidy

# Limpa dependências não usadas e adiciona as faltantes
go mod tidy

7.2 go mod vendor

# Cria diretório vendor com dependências
go mod vendor

# Build usando vendor
go build -mod=vendor

7.3 go list -m all

# Lista todas as dependências e versões resolvidas
go list -m all

# Versão específica
go list -m -versions github.com/gorilla/mux

8. Resolução de Conflitos e Casos Complexos

8.1 Mínima Versão Selecionada (MVS)

O Go utiliza MVS para resolver dependências conflitantes:

// Módulo A requer B v1.0.0
// Módulo C requer B v1.2.0
// Resultado: B v1.2.0 (maior versão compatível)

8.2 Dependências indiretas com replace

// go.mod
require (
    github.com/exemplo/a v1.0.0
)

// github.com/exemplo/a depende de github.com/exemplo/b v1.0.0
// Podemos substituir b mesmo sendo dependência indireta
replace github.com/exemplo/b => github.com/meuuser/b v1.0.1-fork

8.3 Migrando de dep/glide

# Inicializar módulos
go mod init github.com/meuprojeto/novo

# Converter dependências
go mod tidy

# Verificar diferenças
go mod verify

Referências