Go Admin

O que Todo Dev Deve Saber sobre Pacotes e Módulos em Go: go mod, imports e Organização de Projetos Já leu

Entendendo Módulos em Go Um módulo em Go é uma coleção de pacotes Go armazenados em um diretório com um arquivo 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 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 , que cria o arquivo . Este arquivo funciona como um manifesto do seu projeto, similar ao em Node.js ou em Python. O módulo precisa de um nome único, geralmente seguindo a convenção de um caminho de importação (como ). Após executar este comando, você terá um arquivo semelhante a: Este arquivo é fundamental porque documenta exatamente qual versão de cada dependência seu

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.

Referências


Artigos relacionados