Variádico

Cambios al comportamiento de paquetes en Go 1.5

31 de julio de 2015

Hay muchas cosas nuevas que se van a estrenar en Go 1.5, pero una de las características que más me llama la atención es el cambio a la gestión de paquetes. Específicamente, se van a añadir dos directorios especiales: internal y vendor. Cada uno resuelve un problema diferente.

El problema del ámbito de paquetes

¿Sabías que, en Go 1.4 y menor, si exportas una variable o función en un paquete, todos los paquetes en el $GOPATH tienen acceso a esa variable o función? Así es, amigos.

Por ejemplo, digamos que creaste una aplicación con ésta estructura.

miapli/
├── config/
│   └── config.go
└── servidor.go

En config.go, hay unas variables exportadas para que las puedas usar en servidor.go.

// miapli/servidor.go

package main

import (
	"miapli/config"
	"net/http"
)

func main() {
	config.Puerto = ":8080"
	config.Idioma = "español"
	http.ListenAndServe(config.Puerto, nil)
	// más código...
}

Quizá sigas escribiendo tu servidor y ni te das cuenta que ahora todo el $GOPATH puede ver config.Puerto y config.Idioma. Pero si el paquete miapli/config lo hiciste específicamente para miapli, entonces no tiene sentido que todo mundo pueda ver miapli/config.

Solución: limitar el ámbito de paquetes

Para solucionar este problema, Go 1.5 va introducir un directorio especial denominado internal. Para limitar el ámbito de un paquete solamente a tu proyecto, simplemente crea una carpeta llamada internal y mueve tus paquetes allí.

miapli/
├── internal/
│   └── config/
│      └── config.go
└── servidor.go

También tienes que cambiar la ruta de importación en servidor.go.

// miapli/servidor.go

import (
	"miapli/internal/config"
	"net/http"
)

Ahora el paquete miapli/internal/config no se podrá usar fuera de la carpeta miapli/. Si intentas usar miapli/internal/config en otro paquete, entonces go build no compilará tu código.

El problema de gestión de dependencias

Imagina que vas a hacer una aplicación que usa gorilla/sessions. Bien, descargas la dependencia usando go get github.com/gorilla/sessions y empiezas a escribir tu aplicación, la terminas y la publicas en GitHub. Todo va bien—hasta que gorilla/sessions decide introducir unos cambios nuevos y ahora cuando alguien usa go get para descargar tu aplicación, ya no compila. Cuando te pones a investigar por qué no compila tu aplicación, te das cuenta que tú descargaste gorilla/sessions cuando estaba en versión 1, pero ahora cuando tus usuarios descargan gorilla/sessions, ellos obtienen la versión 2.

Solución: carpeta para proveedores

Este es un problema difícil y existen muchísimas herramientas para tratar de resolver este problema. Hasta la fecha, no hay una solución oficial, pero en Go 1.5, el equipo de Go va a experimentar con una solución llamada vendoring.

La idea es que vas a copiar todo el código de proveedores a la carpeta vendor en tu proyecto. De esa forma tendrás control sobre las versiones de dependencias que tu proyecto requiere. Para participar en el experimento, tienes que establecer una variable de entorno.

export GO15VENDOREXPERIMENT=1

Ahora, supongamos que tenemos un archivo así.

// miapli/servidor.go

package main

import (
	"github.com/gorilla/securecookie"
	"github.com/gorilla/sessions"
)

func main() {
	sesiones, err := sessions.NewCookieStore(
		securecookie.GenerateRandomKey(64),
		securecookie.GenerateRandomKey(32),
	)
}

Si no existe una carpeta llamada vendor en la carpeta miapli, entonces go build usará la versión global de gorilla/sessions, es decir, la versión en la ruta $GOPATH/src/github.com/gorilla/sessions. La versión global de un paquete es la que normalmente se usa en Go 1.4 y menor.

Lo interesante ocurre cuando creas una carpeta para proveedores y pones tus dependencias allí.

miapli/
├── servidor.go
└── vendor/
    └── github.com/
        └── gorilla/
            ├── context/
            ├── securecookie/
            └── sessions/

Ahora cuando uses go build para compilar tu proyecto, primero se van a considerar los paquetes en vendor. De esta forma podrás controlar las versiones de tus dependencias.

Es importante decir que no se requieren cambios a las rutas de importación. La lógica es simple: si no tienes una carpeta vendor, entonces github.com/gorilla/sessions se refiere a $GOPATH/src/github.com/gorilla/sessions. Pero si hay una carpeta vendor, entonces github.com/gorilla/sessions se refiere a miapli/vendor/github.com/gorilla/sessions. El archivo miapli/servidor.go de arriba, no requiere ningún cambio.

Muy largo; no leí

Usa internal para limitar el ámbito de paquetes solamente a tu proyecto.

Usa vendor para guardar las dependencias de tu proyecto.