Entendendo Módulos em Go
Um módulo em Go é uma coleção de pacotes Go armazenados em um diretório com um arquivo go.mod na raiz. Este arquivo define o nome do módulo, a versão mínima do Go necessária e todas as dependências externas do projeto. Antes do Go 1.11, a comunidade dependia do GOPATH para gerenciar dependências, um modelo que gerava conflitos de versão e dificultava a manutenção. O sistema de módulos resolveu esse problema de forma elegante e robusta.
Quando você cria um novo projeto Go, o primeiro passo é inicializar um módulo. Isso é feito com o comando go mod init, que cria o arquivo go.mod. Este arquivo funciona como um manifesto do seu projeto, similar ao package.json em Node.js ou requirements.txt em Python. O módulo precisa de um nome único, geralmente seguindo a convenção de um caminho de importação (como github.com/seuusuario/seurepositorio).
go mod init github.com/exemplo/meuprojeto
Após executar este comando, você terá um arquivo go.mod semelhante a:
module github.com/exemplo/meuprojeto
go 1.21
require (
github.com/google/uuid v1.3.0
)
Este arquivo é fundamental porque documenta exatamente qual versão de cada dependência seu projeto utiliza. O Go também cria um arquivo go.sum, que contém os hashes criptográficos de todas as dependências — isso garante que qualquer outro desenvolvedor ou máquina de CI/CD sempre baixe as mesmas versões verificadas.
Sistema de Imports e Pacotes
Em Go, um pacote é a unidade básica de organização de código. Todo arquivo .go pertence a um pacote, declarado na primeira linha não-comentário com package nomedopacote. Pacotes são diretórios, e todos os arquivos em um diretório devem pertencer ao mesmo pacote. Isso é diferente de muitas linguagens onde você pode ter múltiplos módulos em um arquivo.
Quando você quer usar código de outro pacote, você o importa. O sistema de importação em Go é direto: você especifica o caminho do módulo seguido do caminho do pacote. Se você tem um módulo chamado github.com/exemplo/meuprojeto e dentro dele um pacote utils, qualquer outro projeto importaria esse pacote como:
import "github.com/exemplo/meuprojeto/utils"
A partir daí, você acessa as funções, tipos e constantes do pacote utils usando a notação ponto. É importante notar que em Go, apenas identificadores que começam com letra maiúscula são exportados (públicos). Isso é uma convenção aplicada rigidamente pelo compilador — não há palavra-chave public ou private.
// arquivo: main.go
package main
import (
"fmt"
"github.com/exemplo/meuprojeto/utils"
)
func main() {
resultado := utils.SomarNumeros(5, 3) // SomarNumeros é exportada (maiúscula)
fmt.Println(resultado)
}
// arquivo: utils/math.go
package utils
// Exportada (visível para outros pacotes)
func SomarNumeros(a, b int) int {
return a + b
}
// Não exportada (visível apenas dentro do pacote utils)
func validarNumeros(a, b int) bool {
return a > 0 && b > 0
}
Você também pode importar apenas para efeitos colaterais usando _, ou dar um alias a um pacote para evitar conflitos de nome:
import (
"database/sql"
_ "github.com/lib/pq" // executa init() mas não usa funções
m "math" // alias para evitar conflito
)
Organização Prática de Projetos
A organização de diretórios em um projeto Go segue padrões bem estabelecidos na comunidade. A estrutura típica de um projeto profissional tem diretórios com propósitos específicos: cmd/ para aplicações executáveis, pkg/ para código reutilizável, internal/ para código que não deve ser importado por outros projetos, e test/ ou testdata/ para testes de integração e dados de teste.
meuapp/
├── go.mod
├── go.sum
├── cmd/
│ ├── meuapp/
│ │ └── main.go
│ └── ferramenta/
│ └── main.go
├── pkg/
│ ├── database/
│ │ ├── db.go
│ │ └── migration.go
│ ├── api/
│ │ ├── handler.go
│ │ └── router.go
│ └── utils/
│ └── validate.go
├── internal/
│ ├── config/
│ │ └── config.go
│ └── service/
│ └── business.go
├── test/
│ └── integration_test.go
└── README.md
O diretório cmd/ contém o ponto de entrada das aplicações. Se você constrói múltiplas ferramentas a partir do mesmo código base, cada uma tem seu próprio subdiretório em cmd/. O diretório pkg/ contém código que você deseja que outros projetos importem — é seguro desde que você mantenha compatibilidade. Já internal/ é especial: Go proíbe que outros módulos importem pacotes dentro de internal/, garantindo que código privado de fato seja privado. Isso é verificado pelo próprio compilador durante go build ou go mod tidy.
Exemplo prático de como importar esses pacotes:
// cmd/meuapp/main.go
package main
import (
"fmt"
"meuapp/pkg/database" // importa da própria aplicação
"meuapp/pkg/api"
)
func main() {
db := database.Connect("postgresql://...")
server := api.NewRouter(db)
server.Start(":8080")
}
// pkg/api/router.go
package api
import (
"meuapp/internal/config" // OK, mesma aplicação
"meuapp/pkg/database"
)
type Router struct {
db *database.Connection
}
func NewRouter(db *database.Connection) *Router {
return &Router{db: db}
}
// internal/config/config.go
package config
func LoadConfig() map[string]string {
return map[string]string{
"database_url": "postgresql://...",
}
}
Se outro projeto tentasse import "meuapp/internal/config", Go recusaria com um erro de compilação. Isso protege a arquitetura do seu projeto.
Gerenciando Dependências com go mod
O comando go mod tidy é seu melhor amigo. Ele remove dependências não utilizadas e adiciona dependências que faltam. Sempre execute-o antes de fazer commit do seu código — garante que go.mod e go.sum estejam sempre sincronizados com o código.
go mod tidy
Para adicionar uma dependência explicitamente, você usa go get. Este comando baixa a versão especificada e atualiza os arquivos go.mod e go.sum:
go get github.com/gorilla/mux@v1.8.0
go get github.com/lib/pq@latest
go get -u ./... # atualiza todas as dependências diretas
Às vezes você quer entender quais dependências seu projeto tem e por quê. O comando go mod graph mostra o grafo de dependências:
go mod graph
A saída mostra as relações: meuapp github.com/gorilla/mux@v1.8.0 significa que seu módulo depende diretamente de mux na versão 1.8.0.
Para verificar se há vulnerabilidades conhecidas em suas dependências:
go list -json -m all | nancy sleuth
(Usando o tool nancy, que você instala com go install github.com/sonatype-nexus-community/nancy@latest)
Um arquivo go.mod bem mantido fica assim:
module github.com/exemplo/meuservidor
go 1.21
require (
github.com/gorilla/mux v1.8.0
github.com/lib/pq v1.10.7
)
require (
github.com/stretchr/testify v1.8.0 // indirect
)
retract v0.0.1 // bug crítico encontrado
A seção require lista dependências diretas que seu código importa. A seção indirect mostra dependências que são necessárias por suas dependências diretas — Go as inclui automaticamente quando você executa go mod tidy. A tag retract permite depreciar versões inteiras, útil quando você descobre um bug crítico após publicar.
Quando você trabalha com versões, Go segue versionamento semântico: v1.2.3 significa major.minor.patch. Uma dependência pode ser atualizada para patch (1.2.4) e minor (1.3.0) sem quebra esperada, mas major (2.0.0) pode ter breaking changes — você precisa atualizar seu código para usar a nova API.