Estructura de Proyecto en Go: Prácticas y Patrones

Estructura tus proyectos de Go para escalabilidad y claridad

Índice

Structurando un proyecto en Go de manera efectiva es fundamental para la mantenibilidad a largo plazo, la colaboración en equipo y la escalabilidad. A diferencia de los frameworks que imponen una estructura de directorios rígida, Go abraza la flexibilidad, pero con esa libertad viene la responsabilidad de elegir patrones que sirvan a las necesidades específicas de tu proyecto.

árbol de proyecto

Entendiendo la filosofía de Go sobre la estructura de proyectos

La filosofía de diseño minimalista de Go se extiende a la organización del proyecto. El lenguaje no impone una estructura específica, confiando en que los desarrolladores tomen decisiones informadas. Este enfoque ha llevado a la comunidad a desarrollar varios patrones probados, desde diseños planos simples para proyectos pequeños hasta arquitecturas sofisticadas para sistemas empresariales.

El principio clave es sencillez primero, complejidad cuando sea necesario. Muchos desarrolladores caen en la trampa de sobre ingeniería su estructura inicial, creando directorios profundamente anidados y abstracciones prematuras. Comienza con lo que necesitas hoy y refactoriza a medida que tu proyecto crece.

¿Cuándo debo usar el directorio internal/ versus pkg/?

El directorio internal/ tiene un propósito específico en el diseño de Go: contiene paquetes que no pueden ser importados por proyectos externos. El compilador de Go impone esta restricción, haciendo que internal/ sea perfecto para lógica privada de la aplicación, reglas de negocio y utilidades que no están destinadas a ser reutilizadas fuera de tu proyecto.

Por otro lado, el directorio pkg/ indica que el código está destinado al consumo externo. Solo coloca código aquí si estás construyendo una biblioteca o componentes reutilizables que otros deben importar. Muchos proyectos no necesitan pkg/ en absoluto: si tu código no está siendo consumido externamente, mantenerlo en la raíz o en internal/ es más limpio. Cuando se construyen bibliotecas reutilizables, considera aprovechar Go generics para crear componentes tipo seguro y reutilizables.

La estructura estándar de un proyecto en Go

El patrón más reconocido es golang-standards/project-layout, aunque es importante señalar que esto no es un estándar oficial. Aquí hay cómo se ve una estructura típica:

myproject/
├── cmd/
│   ├── api/
│   │   └── main.go
│   └── worker/
│       └── main.go
├── internal/
│   ├── auth/
│   ├── storage/
│   └── transport/
├── pkg/
│   ├── logger/
│   └── crypto/
├── api/
│   └── openapi.yaml
├── config/
│   └── config.yaml
├── scripts/
│   └── deploy.sh
├── go.mod
├── go.sum
└── README.md

El directorio cmd/

El directorio cmd/ contiene los puntos de entrada de tu aplicación. Cada subdirectorio representa un binario ejecutable separado. Por ejemplo, cmd/api/main.go construye tu servidor de API, mientras que cmd/worker/main.go podría construir un procesador de trabajos en segundo plano.

Mejor práctica: Mantén tus archivos main.go lo más mínimos posible—solo lo suficiente para conectar dependencias, cargar la configuración y iniciar la aplicación. Toda la lógica sustancial pertenece a paquetes que main.go importa.

// cmd/api/main.go
package main

import (
    "log"
    "myproject/internal/server"
    "myproject/internal/config"
)

func main() {
    cfg, err := config.Load()
    if err != nil {
        log.Fatal(err)
    }
    
    srv := server.New(cfg)
    if err := srv.Start(); err != nil {
        log.Fatal(err)
    }
}

El directorio internal/

Este es donde vive tu código privado de la aplicación. El compilador de Go impide que cualquier proyecto externo importe paquetes dentro de internal/, lo que lo hace ideal para:

  • Lógica de negocio y modelos de dominio
  • Servicios de aplicación
  • APIs internas e interfaces
  • Repositorios de base de datos (para elegir el ORM adecuado, consulta nuestra comparación de ORMs de Go para PostgreSQL)
  • Lógica de autenticación y autorización

Organiza internal/ por funcionalidad o dominio, no por capa técnica. En lugar de internal/handlers/, internal/services/, internal/repositories/, prefiere internal/user/, internal/order/, internal/payment/ donde cada paquete contiene sus propios manejadores, servicios y repositorios.

¿Debo comenzar con una estructura de directorios compleja?

Absolutamente no. Si estás construyendo una herramienta pequeña o un prototipo, comienza con:

myproject/
├── main.go
├── go.mod
└── go.sum

A medida que tu proyecto crece y identificas agrupaciones lógicas, introduce directorios. Podrías añadir un paquete db/ cuando la lógica de base de datos se vuelva sustancial, o un paquete api/ cuando los manejadores HTTP se multipliquen. Deja que la estructura surja naturalmente en lugar de imponerla desde el principio.

Estructuras planas versus anidadas: Encontrando el equilibrio

Uno de los errores más comunes en la estructura de proyectos en Go es el exceso de anidamiento. Go favorece jerarquías poco profundas—normalmente una o dos capas. El anidamiento profundo aumenta la carga cognitiva y hace que las importaciones sean incómodas.

¿Cuáles son los errores más comunes?

Anidamiento excesivo de directorios: Evita estructuras como internal/services/user/handlers/http/v1/. Esto crea una complejidad innecesaria en la navegación. En su lugar, usa internal/user/handler.go.

Nombres de paquetes genéricos: Nombres como utils, helpers, common o base son olores a código. No transmiten funcionalidad específica y suelen convertirse en vertederos para código no relacionado. Usa nombres descriptivos: validator, auth, storage, cache.

Dependencias circulares: Cuando el paquete A importa el paquete B y B importa A, tienes una dependencia circular—un error de compilación en Go. Esto normalmente señala una mala separación de preocupaciones. Introduce interfaces o extrae tipos compartidos en un paquete separado.

Mezcla de preocupaciones: Mantén los manejadores HTTP enfocados en preocupaciones HTTP, los repositorios de base de datos en el acceso a datos y la lógica de negocio en paquetes de servicio. Colocar reglas de negocio en manejadores dificulta las pruebas y acopla tu lógica de dominio a HTTP.

Diseño orientado al dominio y arquitectura hexagonal

Para aplicaciones más grandes, especialmente microservicios, el Diseño Orientado al Dominio (DDD) con Arquitectura Hexagonal proporciona una clara separación de preocupaciones.

¿Cómo estructuro un microservicio siguiendo el Diseño Orientado al Dominio?

La Arquitectura Hexagonal organiza el código en capas concéntricas con dependencias que fluyen hacia adentro:

internal/
├── domain/
│   └── user/
│       ├── entity.go        # Modelos de dominio y objetos de valor
│       ├── repository.go    # Interfaz de repositorio (puerto)
│       └── service.go       # Servicios de dominio
├── application/
│   └── user/
│       ├── create_user.go   # Caso de uso: crear usuario
│       ├── get_user.go      # Caso de uso: recuperar usuario
│       └── service.go       # Orquestación del servicio de aplicación
├── adapter/
│   ├── http/
│   │   └── user_handler.go  # Adaptador HTTP (extremos REST)
│   ├── postgres/
│   │   └── user_repo.go     # Adaptador de base de datos (implementa el puerto de repositorio)
│   └── redis/
│       └── cache.go         # Adaptador de caché
└── api/
    └── http/
        └── router.go        # Configuración de rutas y middleware

Capa de dominio (domain/): Lógica de negocio central, entidades, objetos de valor e interfaces de servicio de dominio. Esta capa no tiene dependencias en sistemas externos—ningún HTTP, ninguna importación de base de datos. Define interfaces de repositorio (puertos) que los adaptadores implementan.

Capa de aplicación (application/): Casos de uso que orquestan objetos de dominio. Cada caso de uso (por ejemplo, “crear usuario”, “procesar pago”) es un archivo o paquete separado. Esta capa coordina objetos de dominio pero no contiene reglas de negocio por sí misma.

Capa de adaptador (adapter/): Implementa interfaces definidas por capas internas. Los manejadores HTTP convierten solicitudes en objetos de dominio, los repositorios de base de datos implementan persistencia, las colas de mensajes manejan comunicación asincrónica. Esta capa contiene todo el código específico del framework e infraestructura.

Capa de API (api/): Rutas, middleware, DTOs (Objetos de Transferencia de Datos), versionado de API y especificaciones de OpenAPI.

Esta estructura asegura que tu lógica de negocio central permanezca testable e independiente de frameworks, bases de datos o servicios externos. Puedes cambiar PostgreSQL por MongoDB, o REST por gRPC, sin tocar el código del dominio.

Patrones prácticos para diferentes tipos de proyectos

Herramienta CLI pequeña

Para aplicaciones de línea de comandos, querrás una estructura que soporte múltiples comandos y subcomandos. Considera usar marcos como Cobra para la estructura de comandos y Viper para la gestión de configuración. Nuestra guía sobre construir aplicaciones CLI en Go con Cobra y Viper cubre este patrón en detalle.

mytool/
├── main.go
├── command/
│   ├── root.go
│   └── version.go
├── go.mod
└── README.md

Servicio de API REST

Cuando se construyen APIs REST en Go, esta estructura separa las preocupaciones de forma clara: los manejadores manejan preocupaciones HTTP, los servicios contienen lógica de negocio y los repositorios gestionan el acceso a datos. Para una guía completa que cubra enfoques con la biblioteca estándar, frameworks, autenticación, patrones de prueba y mejores prácticas para servicios backend escalables, consulta nuestra guía completa para construir APIs REST en Go.

myapi/
├── cmd/
│   └── api/
│       └── main.go
├── internal/
│   ├── config/
│   ├── middleware/
│   ├── user/
│   │   ├── handler.go
│   │   ├── service.go
│   │   └── repository.go
│   └── product/
│       ├── handler.go
│       ├── service.go
│       └── repository.go
├── pkg/
│   └── httputil/
├── go.mod
└── README.md

Monorepo con múltiples servicios

myproject/
├── cmd/
│   ├── api/
│   ├── worker/
│   └── scheduler/
├── internal/
│   ├── shared/        # Paquetes internos compartidos
│   ├── api/          # Código específico de API
│   ├── worker/       # Código específico de worker
│   └── scheduler/    # Código específico de scheduler
├── pkg/              # Bibliotecas compartidas
├── go.work           # Archivo de workspace de Go
└── README.md

Pruebas y documentación

Coloca los archivos de prueba junto al código que prueban usando el sufijo _test.go:

internal/
└── user/
    ├── service.go
    ├── service_test.go
    ├── repository.go
    └── repository_test.go

Esta convención mantiene las pruebas cerca de la implementación, facilitando su búsqueda y mantenimiento. Para pruebas de integración que tocan múltiples paquetes, crea un directorio test/ separado en la raíz del proyecto. Para una guía completa sobre cómo escribir pruebas unitarias efectivas, incluyendo pruebas basadas en tablas, mocks, análisis de cobertura y mejores prácticas, consulta nuestra guía sobre la estructura y mejores prácticas de pruebas unitarias en Go.

La documentación debe estar en:

  • README.md: Visión general del proyecto, instrucciones de configuración, uso básico
  • docs/: Documentación detallada, decisiones de arquitectura, referencias de API
  • api/: Especificaciones de OpenAPI/Swagger, definiciones de protobuf

Para APIs REST, generar y servir documentación OpenAPI con Swagger es esencial para la descubribilidad de la API y la experiencia del desarrollador. Nuestra guía sobre añadir Swagger a tu API en Go cubre la integración con marcos populares y mejores prácticas.

Gestión de dependencias con Go Modules

Cada proyecto en Go debe usar Go Modules para la gestión de dependencias. Para una referencia completa sobre los comandos de Go y la gestión de módulos, consulta nuestra Hoja de trucos de Go. Inicializa con:

go mod init github.com/yourusername/myproject

Esto crea go.mod (dependencias y versiones) y go.sum (checksums para verificación). Mantén estos archivos en control de versiones para construcciones reproducibles.

Actualiza las dependencias regularmente:

go get -u ./...          # Actualiza todas las dependencias
go mod tidy              # Elimina dependencias no utilizadas
go mod verify            # Verifica checksums

Conclusión clave

  1. Comienza simple, evoluciona naturalmente: No sobre ingenierías tu estructura inicial. Añade directorios y paquetes cuando la complejidad lo requiera.

  2. Preferir jerarquías planas: Limita el anidamiento a una o dos capas. La estructura plana de paquetes en Go mejora la legibilidad.

  3. Usa nombres de paquetes descriptivos: Evita nombres genéricos como utils. Nombra paquetes según lo que hacen: auth, storage, validator.

  4. Separa claramente las preocupaciones: Mantén los manejadores enfocados en HTTP, los repositorios en el acceso a datos y la lógica de negocio en paquetes de servicio.

  5. Usa internal/ para privacidad: Úsalo para código que no debe ser importado externamente. La mayor parte del código de la aplicación pertenece aquí.

  6. Aplica patrones de arquitectura cuando sea necesario: Para sistemas complejos, la Arquitectura Hexagonal y el DDD proporcionan límites claros y testabilidad.

  7. Deja que Go te guíe: Sigue los idiomas de Go en lugar de importar patrones de otros lenguajes. Go tiene su propia filosofía sobre simplicidad y organización.

Enlaces útiles

Otros artículos relacionados