Una de las ideas más prácticas de Go es que la ruta de un import es, a la vez, el nombre del paquete y el sitio de donde descargarlo. Escribes import "github.com/usuario/libreria" y el comando go sabe que tiene que ir a GitHub y clonar ese repositorio con git. No hace falta un registro central como npm o Packagist, y cualquiera sabe dónde abrir un issue.
El problema es que esa comodidad tiene letra pequeña, y la explica muy bien Iain Cambridge en Don’t couple your Go code to GitHub: si la ruta del import es la dirección de tu hosting, tu código queda atado a ese hosting.
El problema
Imagina que tu empresa decide pasar de GitHub a GitLab. En casi cualquier otro lenguaje sería mover repositorios y actualizar la CI. En Go, además, hay que cambiar el module de cada go.mod y todos los imports de todo el código que use esas librerías, porque github.com/empresa/lib ya no apunta al sitio correcto. Y si alguien se olvida de algo, seguirá descargando la versión antigua.
Cuando tienes decenas de servicios y librerías internas, ese cambio se convierte en un proyecto que nunca encuentra hueco. El autor cuenta el caso de una empresa que acabó trabajando con GitLab, GitHub y Azure DevOps a la vez porque migrar era una tarea tan grande que “no tenían tiempo”. Resultado: tres plataformas pagadas en paralelo porque la ruta de los imports decidía por ellos.
Dicho así suena absurdo, pero en la comunidad Go es prácticamente lo habitual.
La solución: un dominio propio
La alternativa es usar un dominio tuyo como nombre de los módulos. Es lo que hacen, por ejemplo, go.uber.org o go.mongodb.org:
import "go.uber.org/zap"
Ese dominio no aloja el código. Sólo le dice al comando go dónde está. Si mañana el repositorio se mueve a GitLab, se cambia esa indicación y nadie tiene que tocar ni una línea: los imports y el go get siguen siendo los mismos.
flowchart LR
A["go get go.empresa.com/lib"] -->|"?go-get=1"| B["go.empresa.com · meta go-import"]
B -->|hoy| C["github.com/empresa/lib"]
B -.->|mañana| D["gitlab.com/empresa/lib"]El mecanismo es sencillo. Cuando el comando go encuentra una ruta que no conoce, pide la URL añadiendo ?go-get=1 y busca en el HTML una etiqueta <meta name="go-import"> que le dice qué sistema de control de versiones usar y dónde está el repositorio.
La configuración
El artículo trae la configuración que usa el autor para su propio dominio, go.iain.rocks. La parte de nginx distingue entre personas y el comando go: a las personas las redirige al repositorio y al comando go le sirve el HTML con las etiquetas.
server {
server_name go.iain.rocks;
root /var/www/go.iain.rocks;
index index.html;
location / {
# Si no es el comando go, redirigir al repositorio
if ($args !~ go-get=1) {
return 301 https://github.com/that-guy-iain$request_uri;
}
# Si es el comando go (?go-get=1), servir el HTML
try_files $uri $uri/ =404;
}
listen 443 ssl;
# ... configuración SSL de Certbot ...
}
Y el HTML de cada módulo es mínimo:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="go-import" content="go.iain.rocks/boneclone git https://github.com/that-guy-iain/boneclone">
<meta name="go-source" content="go.iain.rocks/boneclone https://github.com/that-guy-iain/boneclone https://github.com/that-guy-iain/boneclone/tree/master{/dir} https://github.com/that-guy-iain/boneclone/blob/master{/dir}/{file}#L{line}">
</head>
<body>
</body>
</html>
La etiqueta go-import tiene tres partes: el prefijo del import (go.iain.rocks/boneclone), el sistema de control de versiones (git) y la URL del repositorio. La go-source es opcional y sirve para que las herramientas de documentación sepan enlazar a directorios, ficheros y líneas concretas del código.
El go.mod del módulo, lógicamente, tiene que declarar la ruta nueva:
module go.iain.rocks/boneclone
No hace falta nginx para esto. Como son páginas estáticas, sirve cualquier hosting estático donde puedas poner un dominio, siempre que responda con esas etiquetas.
Lo que hay que tener en cuenta
El artículo se centra en el porqué y en la configuración, así que añado algunas cosas que conviene tener presentes:
- El dominio pasa a ser una dependencia más. Si caduca o el servidor se cae, las descargas directas fallan. Para módulos públicos,
proxy.golang.orgguarda en caché las versiones ya publicadas y amortigua bastante el problema, pero el dominio hay que cuidarlo igual que cuidas el repositorio. - Para librerías internas, con repositorios privados, hay que configurar
GOPRIVATE(por ejemploGOPRIVATE=go.empresa.com/*) para que el comandogono intente pasar por el proxy público ni por la base de datos de checksums, y dar acceso a git a los repositorios. - Cambiar de nombre a un módulo existente rompe a quien lo usa. Lo ideal es hacerlo al empezar el proyecto. Si ya tienes usuarios, lo razonable es sacar una nueva versión con la ruta nueva y avisar, porque la ruta antigua seguirá funcionando para las versiones anteriores.
Por qué me parece un buen consejo
No es una idea nueva, pero sí de esas que se olvidan porque el camino por defecto funciona bien… hasta que deja de hacerlo. Crear un repositorio en GitHub y usar su URL como nombre del módulo es lo que hace casi todo el mundo, y durante años no pasa nada.
Para proyectos personales pequeños probablemente no merezca la pena. Para cualquier equipo que tenga librerías internas en Go, montar un dominio go.empresa.com es cuestión de una tarde, y a cambio la decisión de dónde alojar el código vuelve a ser sólo eso: una decisión de hosting, no una refactorización de todos los repositorios.




