Cuando trabajas en un proyecto complejo, a menudo te encuentras desarrollando una librería y, simultáneamente, una aplicación que depende de esa misma librería. Esto crea un problema de flujo de trabajo: para probar un cambio en la librería, tendrías que subirla al repositorio (o al proxy) para que la aplicación pueda verla. Para evitar este ciclo infinito de commit-push-pull, Go introdujo los Workspaces [disponible desde Go 1.18].
Un Workspace es un mecanismo que permite al toolchain de Go tratar múltiples módulos como una unidad de desarrollo local. Funciona mediante un archivo llamado go.work que, en el directorio raíz, le indica al compilador que, en lugar de buscar las dependencias en un registro remoto (como GitHub o un Proxy), debe usar las versiones que tienes en carpetas locales de tu disco duro. Esto es extremadamente útil cuando necesitas realizar cambios estructurales en un módulo compartido y verificar su impacto inmediato en el consumidor sin alterar permanentemente los archivos go.mod.
Sin embargo, no debes confundirlo con la directiva replace de go.mod. Mientras que replace modifica la definición del módulo y debe ser gestionado con cuidado para no romper el build en producción, el Workspace es una configuración puramente local para tu entorno de desarrollo. Si usas replace para desarrollo y olvidas revertirlo, romperás el código para todo el equipo; si usas go.work correctamente, el riesgo es nulo porque este archivo no afecta la definición del módulo. Pero cuidado: si llegas a cometer el error de incluir go.work en tu control de versiones, romperás el desarrollo de tus compañeros, ya que las rutas locales en ese archivo probablemente no existan en sus máquinas.
// Estructura de archivos para el ejemplo:
// .
// ├── my-lib/
// │ ├── go.mod
// │ └── calculator.go
// ├── my-app/
// │ ├── go.mod
// │ └── main.go
// └── go.work
// --- ARCHIVO: my-lib/go.mod ---
// module example.com/my-lib
// go 1.23
// --- ARCHIVO: my-lib/calculator.go ---
package calculator
// Sumar realiza la suma de dos enteros.
func Sumar(a, b int) int {
return a + b
}
// --- ARCHIVO: my-app/go.mod ---
// module example.com/my-app
// go 1.23
// require example.com/my-lib v0.0.0
// (Nota: La versión es ficticia porque el workspace la sobreescribirá)
// --- ARCHIVO: my-app/main.go ---
package main
import (
"fmt"
"example.com/my-lib" // Importación de nuestro módulo local
)
func main() {
resultado := calculator.Sumar(10, 5)
fmt.Printf("El resultado de la suma es: %d\n", resultado)
}
// --- ARCHIVO: go.work ---
// go 1.23
use (
./my-lib
./my-app
)
Para que este ejemplo funcione, el archivo go.work es el cerebro de la operación. Cuando ejecutas go run ./my-app/main.go desde la raíz, el comando detecta el go.work y realiza lo siguiente:
- Identifica que
example.com/my-libestá presente en la ruta./my-lib. - Sobrescribe la dependencia que
my-app/go.modcree tener (que podría ser una versión remota inexistente o antigua). - Compila ambos módulos como si fueran parte del mismo proyecto.
Si necesitas que las dependencias en tus archivos go.mod reflejen los cambios de versión que estás probando para mantener la consistencia, puedes usar el comando go work sync, el cual sincroniza las versiones de los módulos con lo que el workspace está utilizando actualmente.
El error frecuente
El error más crítico es cometer el comando git add go.work.
Imagina que tu estructura de carpetas es /Users/tu-usuario/dev/proyecto/my-lib. El archivo go.work guardará rutas relativas o absolutas que son específicas de tu entorno. Si subes ese archivo al repositorio, el pipeline de CI/CD o un compañero que trabaje en /home/otro-usuario/proyecto fallará inmediatamente porque el toolchain intentará buscar directorios que no existen en sus máquinas.
Para evitarlo, siempre debes añadir go.work a tu .gitignore global o al .gitignore del proyecto.
Si estás trabajando en múltiples módulos, usa go work init para crear el archivo y go work use <ruta> para añadir módulos nuevos de forma limpia.
N° 102