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?
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\nEn 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\nQué es la arquitectura Hexagonal en palabras simples
\n\nImagina 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\nSi 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\nConceptos principales:
\n- \n
- Port (Puerto) — interfaz que define cómo el mundo exterior puede interactuar con el núcleo. Por ejemplo,
UserRepositorycon los métodosSaveyFindByID. \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
Este enfoque proporciona tres superpoderes: testabilidad (del núcleo sin BD), flexibilidad (reemplazo de cualquier componente) y límites de responsabilidad claros.
\n\nPor qué Go es ideal para Hexagonal
\n\nGo 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\nAdemá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\nEstructura del proyecto: cómo organizar las carpetas
\n\nNo existe un estándar único, pero aquí hay un esquema probado que se usa en proyectos de producción:
\n\nmyapp/\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\nImportante: 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.
Escribimos código: desde interfaces hasta implementación
\n\nVamos a crear un servicio simple para trabajar con usuarios. Empezamos por el núcleo.
\n\nPaso 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)\