El versionado semántico (SemVer) es el estándar que utiliza Go para gestionar la evolución de las librerías mediante el formato vMAJOR.MINOR.PATCH. Sin embargo, Go introduce una capa adicional de seguridad para evitar el “infierno de dependencias” mediante la Import Compatibility Rule.
Esta regla dicta que, a partir de la versión v2.0.0, el module path definido en el archivo go.mod debe incluir un sufijo con el número de la versión mayor (por ejemplo, /v2). Para las versiones v0 (experimental) y v1 (estabilidad inicial), el módulo se identifica simplemente por su ruta original sin sufijo. El diseño de esta regla responde a la necesidad de permitir que un mismo binario pueda importar, simultáneamente, dos versiones distintas de la misma librería. Esto es vital para resolver la dependencia en diamante: si tu proyecto depende de la Librería A (que usa utils/v1) y de la Librería B (que usa utils/v2), el compilador debe tratarlas como paquetes totalmente distintos para evitar colisiones de nombres y conflictos de tipos.
Debes implementar este cambio cuando realices cambios disruptivos (breaking changes), como eliminar funciones, cambiar la firma de un método o modificar el comportamiento de un tipo que rompa la compatibilidad con la versión anterior. Si ignoras esta regla y actualizas el código a una v2 sin cambiar la ruta de importación, romperás todos los proyectos que dependan de tu librería, ya que el sistema de módulos intentará tratar tu versión disruptiva como si fuera una actualización menor de la v1.
package main
import (
"fmt"
)
// Simulamos el comportamiento de una librería que ha evolucionado.
// En un entorno real, estas estructuras no estarían en el mismo paquete,
// sino en módulos distintos con sus respectivos sufijos de importación.
// Representa la versión v1.0.0 de la librería.
// Su ruta de importación real sería: github.com/example/config
type ConfigV1 struct {
Host string
}
func (c ConfigV1) Connect() string {
return "Conectado a " + c.Host
}
}
// Representa la versión v2.0.0 de la librería.
// Su ruta de importación real sería: github.com/example/config/v2
// El cambio disruptivo es que el método Connect ahora requiere un parámetro.
type ConfigV2 struct {
Host string
Timeout int
}
func (c ConfigV2) Connect(seconds int) string {
return fmt.Sprintf("Conectado a %s con timeout de %ds", c.Host, seconds)
}
}
func main() {
// Gracias a la regla de compatibilidad, el compilador trata a
// ConfigV1 y ConfigV2 como tipos totalmente ajenos, aunque
// provengan del mismo módulo original.
// Uso de la API v1 (sin sufijo en la ruta)
c1 := ConfigV1{Host: "localhost"}
// Uso de la API v2 (con sufijo /v2 en la ruta)
c2 := ConfigV2{Host: "api.produccion.com", Timeout: 30}
// Podemos coexistir sin que el compilador se confunda de tipo
fmt.Println("Resultado v1:", c1.Connect())
fmt.Println("Resultado v2:", c2.Connect(c2.Timeout))
}
Desglose del ejemplo
En el código anterior, hemos simulado lo que sucede en un proyecto real cuando conviven dos versiones de una dependencia.
- Simulación de tipos: Aunque en este ejemplo
ConfigV1yConfigV2están en el mismopackage mainpara que el código sea ejecutable, en la práctica representan dos módulos distintos. El compilador de Go utiliza la ruta de importación para determinar la identidad de un tipo. - Identidad de tipo: Nota cómo
c1.Connect()no recibe argumentos, mientras quec2.Connect(seconds)sí. Si no existiera el sufijo/v2en la ruta de importación de la segunda versión, el compilador no podría distinguir entre elConfigde lav1y el de lav2si ambos se usaran en el mismo contexto, resultando en errores de compilación imposibles de resolver sin el sufijo. - Coexistencia: La función
maindemuestra que es posible instanciar y operar con ambos objetos sin conflictos. Para el runtime,ConfigV1yConfigV2son tipos con nombres y estructuras de memoria diferentes gracias a que sus “caminos” de importación son distintos.
El error frecuente
El error más grave al gestionar versiones es realizar un “bump” a v2 en el código fuente pero olvidar actualizar el archivo go.mod.
Si cambias la firma de una función en tu código pero mantienes module github.com/user/lib en tu go.mod, estás rompiendo el contrato de SemVer. Cuando un usuario haga go get -u, su compilador descargará tu nueva versión, pero como la ruta de importación sigue siendo la misma, el compilador intentará aplicarla a su código antiguo, provocando errores de “tipo incompatible” o “función no encontrada”.
Para hacer una transición correcta a v2:
1. Cambia el nombre del módulo en go.mod añadiendo /v2.
2. Actualiza todos los archivos .go de tu librería para que sus propios import apunten a la nueva ruta con /v2.
3. En el proyecto que consume la librería, deberás actualizar explícitamente sus importaciones para usar la nueva ruta.
N° 99