Adaptarse a esa estructura acelera la incorporación y mantiene claros los límites de los módulos.
Coloca un paquete main por comando bajo cmd/<nombre>/.
Mantén los detalles de implementación en internal/ para que otros módulos no puedan importarlos.
Las bibliotecas destinadas a la reutilización externa viven en paquetes de nivel superior con nombres claros o bajo pkg/ cuando deseas una zona pública obvia.
Un módulo puede alojar múltiples comandos y paquetes compartidos; divide los módulos solo cuando las líneas de lanzamiento diverjan.
Tarjeta de receta de referencia rápida - lista para copiar y pegar.
example.com/shop/
go.mod
cmd/
shopd/main.go
migrate/main.go
internal/
api/
store/
pkg/client/ # SDK estable opcional
// cmd/shopd/main.go
package main
import " example.com/shop/internal/api "
func main () {
api. ListenAndServe ()
}
Cuándo usar esto:
Al iniciar un nuevo servicio HTTP o un worker con más de un binario.
Al publicar una biblioteca consumida por otras empresas o repositorios.
Al refactorizar un repositorio plano donde main y SQL se mezclaban en un solo paquete.
example.com/notify/
go.mod
README.md
cmd/
notifyd/main.go
internal/
config/config.go
server/http.go
queue/worker.go
pkg/client/client.go
// internal/config/config.go
package config
import " os "
func Port () string {
if p := os. Getenv ( "PORT" ); p != "" {
return p
}
return "8080"
}
// internal/server/http.go
package server
import (
" net/http "
" example.com/notify/internal/config "
)
func ListenAndServe () error {
return http. ListenAndServe ( ":" + config. Port (), http. HandlerFunc ( func ( w http . ResponseWriter , r * http . Request ) {
w. Write ([] byte ( "ok" ))
}))
}
// pkg/client/client.go - SDK delgado para otros servicios
package client
import " net/http "
type Client struct { Base string }
func ( c Client ) Ping () ( * http . Response , error ) {
return http. Get (c.Base + "/healthz" )
}
// cmd/notifyd/main.go
package main
import (
" log "
" example.com/notify/internal/server "
)
func main () {
if err := server. ListenAndServe (); err != nil {
log. Fatal (err)
}
}
Lo que esto demuestra:
cmd/notifyd es la única entrada ejecutable; la lógica permanece testeable en internal/server.
pkg/client es opcional pero señala una API soportada para otros módulos.
La configuración y el cableado HTTP son detalles de implementación privados.
cmd/ - Cada subdirectorio es package main que compila un binario.
Los nombres coinciden con el artefacto (shopd, migrate, ctl).
CI mapea go build -o bin/shopd ./cmd/shopd.
internal/ - El compilador bloquea importaciones desde fuera del subárbol del módulo padre.
Úsalo para capas de almacenamiento, adaptadores y cualquier cosa no cubierta por promesas semver.
pkg/ - Convención de repositorios de la era Kubernetes; no está impuesta.
Los importadores externos pueden depender de él, así que trátalo como cualquier paquete público con disciplina de compatibilidad.
Paquetes de dominio de nivel superior - Muchos módulos usan example.com/widget/widget o example.com/widget con paquetes en la raíz en lugar de pkg/.
Elige una estrategia y documéntala en README.
Estructura Mejor para Ten cuidado cmd único + internal Microservicio pequeño Paquetes "dios" en crecimiento cmd/* + internal/* Múltiples binarios Duplicación de flags/configuración pkg/ SDK cliente Bibliotecas de plataforma Cambios de ruptura accidentales Monorepo multi-módulo Etiquetas independientes Sobrecarga de go.work o replace
# Compilar todos los comandos
go build -o bin/ ./cmd/...
# Probar paquetes internos sin exportarlos
go test ./internal/...
// Evita la lógica de negocio en main - mantiene las pruebas rápidas
func main () {
if err := run (); err != nil {
log. Fatal (err)
}
}
Tratar pkg/ como mágicamente estable - Es solo una convención; semver y la documentación hacen que la estabilidad sea real.
Todo en internal/ - Hace imposibles las pruebas de integración en otros repositorios; exporta superficies de cliente mínimas.
Múltiples main en un solo directorio - Inválido; divide los binarios bajo cmd/.
Repositorios planos que crecen indefinidamente - Sin internal/, los ayudantes privados se filtran en las rutas de importación públicas.
Copiar la estructura de Kubernetes ciegamente - Tu servicio puede necesitar un binario, no operadores y CRDs.
Módulos separados por servicio - Aislamiento fuerte en organizaciones grandes.
Paquetes de nivel superior orientados al dominio (billing/, shipping/) sin pkg/ - Claro para bibliotecas medianas.
Plantillas (kubebuilder, cobra) - Generan la estructura cmd/ para dominios de problemas específicos.
¿Es la Estructura de Proyecto Estándar de Go oficial?
No, es una guía de la comunidad.
El blog de Go documenta módulos y paquetes; la estructura es una elección del equipo dentro de las reglas del módulo.
¿Deben las bibliotecas usar pkg/?
No, muchos módulos exportan desde la raíz del módulo o carpetas con nombre.
Usa pkg/ cuando quieras una zona de API externa obvia.
¿Cuántos comandos pertenecen a un solo módulo?
Tantos como compartan código y cadencia de lanzamiento.
Divide los módulos cuando los binarios se lancen independientemente con versiones diferentes.
¿Dónde van las pruebas de integración?
testdata/, internal/..._test.go, o directorios test/ de nivel superior.
Mantén _test.go junto al código para pruebas unitarias; usa etiquetas de compilación para archivos solo de integración.
¿Pueden los paquetes internos importar pkg/?
Sí, el flujo de dependencias es hacia adentro: cmd -> internal -> (opcional) paquetes públicos compartidos.
Evita que pkg importe internal (invierte el modelo).
¿Dónde deben vivir las migraciones?
cmd/migrate, internal/migrate, o archivos SQL en db/migrations/.
Elige una herramienta (golang-migrate, goose) y documéntala.
¿Deben las configuraciones estar en internal/?
Sí, para el análisis de entorno y el cableado de secretos.
Expón solo las estructuras de configuración tipadas necesarias para las pruebas.
¿Qué pasa con api/ vs internal/api/?
Si los manejadores HTTP no son superficies de importación públicas, mantenlos internos.
El SDK público pertenece a pkg/ o a un módulo cliente dedicado.
¿Cómo diseño un CLI con subcomandos?
cmd/tool/main.go delega a internal/cli usando cobra o flag.
Cada subcomando puede ser un archivo, no siempre un binario separado.
¿Afecta la ruta de `go mod init` a la estructura?
La ruta del módulo establece los prefijos de importación.
Los nombres de los directorios deben alinearse con las rutas de importación para mayor claridad.
Versiones de Stack: Esta página fue escrita para Go 1.26.x (Green Tea GC por defecto, go fix modernizadores - verificar parche en la compilación) , chi (última versión - verificar en la compilación), gin (última versión - verificar en la compilación), echo (última versión - verificar en la compilación), google.golang.org/grpc (última versión - verificar en la compilación), sigs.k8s.io/controller-runtime (última versión - verificar en la compilación), kubebuilder (última versión - verificar en la compilación), tinygo (última versión - verificar objetivos de placa en la compilación), wazero (última versión - verificar en la compilación), y golangci-lint (última versión - verificar conjunto de linters en la compilación).
Y29kZWd1aWRlcy5pb3xjZ2lvNTE0fDIwMjYwNw==