Go y arquitectura Hexagonal: escribiendo código que no da miedo modificar

Entrenador de Python en Línea para Principiantes

Aprende Python fácilmente sin sobrecargarte de teoría. Resuelve tareas prácticas con verificación automática, recibe pistas y escribe código directamente en el navegador, sin instalar nada.

Start Course

Conoce: tu código aislado del mundo exterior

\n\n

¿Alguna vez has reescrito la lógica de negocio solo porque cambiaste la base de datos de PostgreSQL a MongoDB? ¿O te has despertado en un sudor frío pensando que necesitas agregar un nuevo endpoint HTTP, y toda la aplicación es un gigantesco main.go de 3000 líneas?

\n\n

Si es así — bienvenido al club. Si no — o llevas poco tiempo escribiendo en Go, o ya estás usando la arquitectura Hexagonal (también conocida como Ports & Adapters).

\n\n

En este artículo analizaremos cómo construir una aplicación en Go que no le teme a los cambios. Ni el cambio de base de datos, ni la migración de REST a gRPC, ni un nuevo framework te obligarán a reescribir el núcleo del sistema. Solo código. Solo Go. Sin rodeos.

\n\n

Qué es la arquitectura Hexagonal en palabras simples

\n\n

Imagina que tu aplicación es una nave espacial. En el centro está la sala de control (lógica de negocio). Alrededor, hay puertos de acoplamiento. A través de los puertos conectas diferentes módulos: motores (base de datos), sensores (peticiones HTTP), comunicación con la Tierra (colas de mensajes).

\n\n

Si decides reemplazar los motores — simplemente desacoplas los viejos y acoplas los nuevos. La sala de control ni siquiera notará el cambio. Esto es la arquitectura Hexagonal.

\n\n

Conceptos principales:

\n
    \n
  • Port (Puerto) — interfaz que define cómo el mundo exterior puede interactuar con el núcleo. Por ejemplo, UserRepository con los métodos Save y FindByID.
  • \n
  • Adapter (Adaptador) — implementación concreta del puerto. Un puerto puede tener muchos adaptadores: PostgreSQLAdapter, InMemoryAdapter, MockAdapter para pruebas.
  • \n
  • Core (Núcleo) — lógica de negocio que no sabe nada del mundo exterior. Funciona solo a través de puertos.
  • \n
\n\n

Este enfoque proporciona tres superpoderes: testabilidad (del núcleo sin BD), flexibilidad (reemplazo de cualquier componente) y límites de responsabilidad claros.

\n\n

Por qué Go es ideal para Hexagonal

\n\n

Go no impone POO, pero proporciona interfaces — lo único necesario para Ports & Adapters. Las interfaces en Go son implícitas: una estructura satisface automáticamente una interfaz si implementa sus métodos. Esto significa que puedes reemplazar un adaptador sin cambiar ni una línea en el núcleo.

\n\n

Además, Go se compila a un binario, lo que hace que la arquitectura sea especialmente útil para microservicios. Y las pruebas integradas y los benchmarks permiten verificar inmediatamente el aislamiento de los componentes.

\n\n

Estructura del proyecto: cómo organizar las carpetas

\n\n

No existe un estándar único, pero aquí hay un esquema probado que se usa en proyectos de producción:

\n\n
myapp/\n├── cmd/\n│   └── app/\n│       └── main.go          # Punto de entrada, construcción de dependencias\n├── internal/\n│   ├── core/\n│   │   ├── domain/          # Entidades, value objects\n│   │   ├── ports/           # Interfaces (puertos)\n│   │   └── services/        # Lógica de negocio (casos de uso)\n│   └── adapters/\n│       ├── api/             # Adaptadores HTTP/gRPC\n│       ├── db/              # Implementaciones de repositorios\n│       └── mq/              # Colas de mensajes\n└── go.mod\n
\n\n

Importante: la carpeta internal prohíbe la importación desde fuera — ideal para proteger el núcleo. Los adaptadores están separados y pueden reemplazarse fácilmente.

\n\n

Escribimos código: desde interfaces hasta implementación

\n\n

Vamos a crear un servicio simple para trabajar con usuarios. Empezamos por el núcleo.

\n\n

Paso 1. Dominio y puertos

\n\n
// internal/core/domain/user.go\npackage domain\n\nimport "time"\n\ntype User struct {\n    ID        string\n    Email     string\n    Name      string\n    CreatedAt time.Time\n}\n\n// internal/core/ports/repositories.go\npackage ports\n\nimport "myapp/internal/core/domain"\n\ntype UserRepository interface {\n    Save(user domain.User) error\n    FindByID(id string) (domain.User, error)\n    FindByEmail(email string) (domain.User, error)\                

Blogs

Book Recommendations