En esta página

Módulos y paquetes

12 min lectura TextoCap. 5 — Go en producción

El sistema de módulos y paquetes de Go

El sistema de módulos de Go, introducido en Go 1.11 y convertido en el estándar en Go 1.16, resuelve uno de los problemas más complicados de cualquier lenguaje: gestión de dependencias reproducible y versionada. Antes de los módulos, Go usaba GOPATH — un sistema que causaba muchos problemas con versiones de dependencias.

Entender la diferencia entre módulos y paquetes es fundamental:

  • Paquete: directorio con archivos .go que comparten el mismo package nombre. Es la unidad de organización de código.
  • Módulo: colección de paquetes con un mismo prefijo de importación y un archivo go.mod. Es la unidad de distribución y versionado.

Inicializar un módulo

# Crear directorio del proyecto
mkdir mi-api
cd mi-api

# Inicializar el módulo con el path de importación
go mod init github.com/tuusuario/mi-api

# Resultado: archivo go.mod creado
cat go.mod
module github.com/tuusuario/mi-api

go 1.26

El module path es el identificador único de tu módulo. Por convención usa el path del repositorio (GitHub, GitLab, etc.), pero puede ser cualquier string.

Estructura de proyecto idiomática

mi-api/
├── go.mod
├── go.sum
├── main.go                 ← punto de entrada
├── cmd/
│   ├── server/
│   │   └── main.go        ← servidor HTTP
│   └── migrate/
│       └── main.go        ← herramienta CLI de migraciones
├── internal/               ← paquetes privados (no importables desde fuera del módulo)
│   ├── config/
│   │   └── config.go
│   ├── handler/
│   │   ├── user.go
│   │   └── user_test.go
│   └── repository/
│       └── user.go
├── pkg/                    ← paquetes públicos (importables por otros módulos)
│   └── validator/
│       └── validator.go
└── api/
    └── openapi.yaml

Esta estructura sigue el Standard Go Project Layout, aunque para proyectos pequeños es perfectamente aceptable tener todo en el directorio raíz.

Paquetes: la unidad de organización

Todos los archivos .go en el mismo directorio deben declarar el mismo nombre de paquete:

// usuario/usuario.go
package usuario

type Usuario struct {
    ID     int
    Nombre string
}

// usuario/repositorio.go
package usuario  // mismo directorio, mismo package

type Repositorio struct {
    db *sql.DB
}

Identificadores exportados vs. privados

En Go, la visibilidad se controla con la capitalización del primer carácter:

package mypackage

// EXPORTADO — visible desde fuera del paquete
type Usuario struct {
    ID     int    // campo exportado
    Nombre string // campo exportado
    email  string // campo privado — minúscula
}

// EXPORTADO
func NuevoUsuario(nombre, email string) *Usuario {
    return &Usuario{Nombre: nombre, email: email}
}

// EXPORTADO
const MaxUsuarios = 1000

// EXPORTADO
var ErrNoEncontrado = errors.New("usuario no encontrado")

// NO exportado — solo visible en este paquete
func validarEmail(email string) bool {
    return strings.Contains(email, "@")
}

// NO exportado
type configInterna struct {
    timeout int
    retries int
}

Importar paquetes

// Importación estándar
import "fmt"
import "os"
import "net/http"

// Importación en bloque (preferida)
import (
    "fmt"
    "os"
    "net/http"
    "time"

    // Dependencias externas — van en un grupo separado por convención
    "github.com/gin-gonic/gin"
    "gorm.io/gorm"
)

// Alias para evitar colisiones de nombres
import (
    mathRand "math/rand"
    cryptoRand "crypto/rand"
)

// Importación para efectos secundarios (solo init())
import _ "github.com/lib/pq"  // registrar driver PostgreSQL

// Importar solo en el mismo paquete (menos común)
import . "fmt"  // permite usar Println en lugar de fmt.Println

El directorio `internal`

Go tiene una regla especial: los paquetes dentro de un directorio internal solo pueden ser importados por código dentro del árbol de directorios padre:

mi-app/
├── internal/
│   └── config/       ← solo importable desde mi-app/
│       └── config.go
└── cmd/
    └── server/
        └── main.go   ← puede importar mi-app/internal/config

Esto te permite tener APIs internas sin exponerlas públicamente — esencial para proyectos grandes.

Gestión de dependencias

Agregar una dependencia

# Agregar la última versión
go get github.com/gin-gonic/gin

# Agregar versión específica
go get github.com/gin-gonic/[email protected]

# Agregar la última versión pre-release
go get github.com/gin-gonic/gin@latest

# Actualizar todas las dependencias a parches/menores
go get -u ./...

# Solo actualizar parches (más seguro)
go get -u=patch ./...

Remover una dependencia

# Después de eliminar los imports del código:
go mod tidy

El archivo `go.mod`

module github.com/tuusuario/mi-api

go 1.26

require (
    github.com/gin-gonic/gin v1.10.0
    github.com/go-playground/validator/v10 v10.22.0
    gorm.io/driver/postgres v1.5.9
    gorm.io/gorm v1.25.12
)

require (
    // dependencias indirectas (manejadas automáticamente)
    golang.org/x/crypto v0.26.0 // indirect
    ...
)

El archivo `go.sum`

go.sum contiene los hashes criptográficos de cada dependencia. Go verifica estas sumas antes de usar cualquier módulo, garantizando la integridad de las dependencias. Nunca edites go.sum manualmente — Go lo gestiona automáticamente.

Comandos esenciales de módulos

# Ver todas las dependencias (directas e indirectas)
go list -m all

# Ver por qué una dependencia está incluida
go mod why github.com/gin-gonic/gin

# Verificar integridad de dependencias
go mod verify

# Descargar todas las dependencias al cache local
go mod download

# Crear un vendor/ directorio (para builds reproducibles sin red)
go mod vendor

# Limpiar el cache de módulos
go clean -modcache

Instalando ejecutables con `go install`

# Instalar un ejecutable Go
go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest

# El binario se instala en $GOPATH/bin (o $GOBIN)
# Asegúrate de que $GOPATH/bin está en tu PATH
which golangci-lint

Paquetes especiales

`package main`: el punto de entrada

Todo programa Go ejecutable tiene exactamente un package main con una función main():

// main.go
package main

import "fmt"

func main() {
    fmt.Println("¡Hola, mundo!")
}

La función `init()`

Cada paquete puede tener una o más funciones init() que se ejecutan automáticamente al cargar el paquete, antes de main():

package config

var configGlobal *Config

func init() {
    // Esta función se ejecuta automáticamente
    configGlobal = cargarConfigDesdeEnv()
}

El orden de ejecución: todas las funciones init() de las dependencias → init() del paquete → main().

Organización de pruebas

Las pruebas en Go viven junto al código que prueban, en archivos _test.go:

paquete/
├── usuario.go
└── usuario_test.go pruebas en el mismo directorio

# Ejecutar pruebas
go test ./...          # todos los paquetes
go test ./internal/... # solo internal
go test -v ./...       # verbose
go test -cover ./...   # con cobertura

Con el sistema de módulos dominado, en la siguiente lección construiremos APIs REST con el poderoso servidor HTTP de la biblioteca estándar de Go.

El nombre del paquete debe coincidir con el directorio
Por convención (y para que las herramientas funcionen correctamente), el nombre del paquete declarado en package X debe coincidir con el nombre del directorio que contiene los archivos. La única excepción es package main, que puede estar en cualquier directorio y es el punto de entrada del programa.
go mod tidy mantiene go.mod y go.sum limpios
Ejecuta go mod tidy regularmente. Agrega las dependencias que faltan en go.mod y go.sum, y elimina las que ya no se usan. Es como npm install pero más inteligente: también limpia dependencias huérfanas. Ejecútalo siempre antes de hacer commit.