Introducci贸n a los m贸dulos Go

El pr贸ximo lanzamiento de la versi贸n 1.11 del lenguaje de programaci贸n Go traer谩 soporte experimental para m贸dulos , un nuevo sistema de administraci贸n de dependencias para Go. (traducci贸n de la nota: la liberaci贸n tuvo lugar )


Recientemente, ya escrib铆 una peque帽a publicaci贸n sobre esto . Desde entonces, algo ha cambiado ligeramente y nos hemos acercado al lanzamiento, por lo que me parece que ha llegado el momento de un nuevo art铆culo, agreguemos m谩s pr谩ctica.


Entonces, esto es lo que haremos: crear un nuevo paquete y luego hacer algunos lanzamientos para ver c贸mo funciona.


Creaci贸n del m贸dulo


Primero, crea nuestro paquete. Llam茅moslo testmod. Detalle importante: el directorio del paquete debe colocarse fuera de su $GOPATH , porque, dentro de 茅l, el soporte del m贸dulo est谩 deshabilitado de forma predeterminada . Los m贸dulos Go son el primer paso hacia un abandono completo de $GOPATH en el futuro.


 $ mkdir testmod $ cd testmod 

Nuestro paquete es bastante simple:


 package testmod import "fmt" // Hi returns a friendly greeting func Hi(name string) string { return fmt.Sprintf("Hi, %s", name) } 

El paquete est谩 listo, pero a煤n no es un m贸dulo . Vamos a arreglarlo


 $ go mod init github.com/robteix/testmod go: creating new go.mod: module github.com/robteix/testmod 

Tenemos un nuevo archivo llamado go.mod en el directorio del paquete con los siguientes contenidos:


 module github.com/robteix/testmod 

Un poco, pero eso es lo que convierte nuestro paquete en un m贸dulo .


Ahora podemos insertar este c贸digo en el repositorio:


 $ git init $ git add * $ git commit -am "First commit" $ git push -u origin master 

Hasta ahora, cualquier persona que quiera usar nuestro paquete aplicar铆a go get :


 $ go get github.com/robteix/testmod 

Y este comando traer铆a el 煤ltimo c贸digo de la rama master . Esta opci贸n a煤n funciona, pero ser铆a mejor si ya no lo hacemos, porque ahora "hay una mejor manera". Tomar c贸digo directamente de la rama master es, de hecho, peligroso, ya que nunca sabemos con certeza si los autores del paquete no hicieron cambios que "rompan" nuestro c贸digo. Para resolver este problema, se inventaron los m贸dulos Go.


Una peque帽a digresi贸n sobre los m贸dulos de versiones


Los m贸dulos Go est谩n versionados, adem谩s hay cierta especificidad de las versiones individuales. Tendr谩 que familiarizarse con los conceptos que subyacen a las versiones sem谩nticas .


Adem谩s, Go usa etiquetas de repositorio cuando busca versiones, y algunas versiones difieren del resto: por ejemplo, las versiones 2 y m谩s deben tener una ruta de importaci贸n diferente que para las versiones 0 y 1 (llegaremos a esto).


Por defecto, Go descarga la 煤ltima versi贸n, que tiene una etiqueta disponible en el repositorio.
Esta es una caracter铆stica importante, ya que se puede usar cuando se trabaja con la rama master .


Para nosotros ahora es importante que al crear el lanzamiento de nuestro paquete, necesitemos poner una etiqueta con la versi贸n en el repositorio.


Hag谩moslo


Haciendo tu primer lanzamiento


Nuestro paquete est谩 listo y podemos "extenderlo" a todo el mundo. Hacemos esto usando etiquetas versionadas. Deje que el n煤mero de versi贸n sea 1.0.0:


 $ git tag v1.0.0 $ git push --tags 

Estos comandos crean una etiqueta en mi repositorio de Github que marca la confirmaci贸n actual como versi贸n 1.0.0.


Go no insiste en esto, pero es una buena idea crear una nueva rama adicional ("v1") a la que podamos enviar parches.


 $ git checkout -b v1 $ git push -u origin v1 

Ahora podemos trabajar en la rama master sin preocuparnos de que podamos romper nuestra versi贸n.


Usando nuestro m贸dulo


Usemos el m贸dulo creado. Escribiremos un programa simple que importe nuestro nuevo paquete:


 package main import ( "fmt" "github.com/robteix/testmod" ) func main() { fmt.Println(testmod.Hi("roberto")) } 

Hasta ahora, ejecutar铆a go get github.com/robteix/testmod para descargar el paquete, pero con los m贸dulos se vuelve m谩s interesante. Primero, necesitamos habilitar el soporte de m贸dulos en nuestro nuevo programa.


 $ go mod init mod 

Como probablemente esperaba, seg煤n lo que ley贸 anteriormente, apareci贸 un nuevo archivo go.mod en el directorio con el nombre del m贸dulo dentro:


 module mod 

La situaci贸n se vuelve a煤n m谩s interesante cuando intentamos armar nuestro programa:


 $ go build go: finding github.com/robteix/testmod v1.0.0 go: downloading github.com/robteix/testmod v1.0.0 

Como puede ver, el comando go encontr贸 y descarg贸 autom谩ticamente el paquete importado por nuestro programa.
Si revisamos nuestro archivo go.mod , veremos que algo ha cambiado:


 module mod require github.com/robteix/testmod v1.0.0 

Y tenemos otro nuevo archivo llamado go.sum , que contiene los hash de los paquetes para verificar la versi贸n y los archivos correctos.


 github.com/robteix/testmod v1.0.0 h1:9EdH0EArQ/rkpss9Tj8gUnwx3w5p0jkzJrd5tRAhxnA= github.com/robteix/testmod v1.0.0/go.mod h1:UVhi5McON9ZLc5kl5iN2bTXlL6ylcxE9VInV71RrlO8= 

Hacer una versi贸n de lanzamiento de correcci贸n de errores


Ahora, supongamos que encontramos un problema en nuestro paquete: 隆no hay puntuaci贸n en el saludo!
Algunas personas se pondr谩n furiosas porque nuestro saludo amistoso ya no es tan amistoso.
Arreglemos esto y publiquemos una nueva versi贸n:


 // Hi returns a friendly greeting func Hi(name string) string { - return fmt.Sprintf("Hi, %s", name) + return fmt.Sprintf("Hi, %s!", name) } 

Hicimos este cambio directamente en la rama v1 , porque no tiene nada que ver con lo que haremos a continuaci贸n en la rama v2 , pero en la vida real, tal vez deber铆a hacer estos cambios en master y luego transferirlos a v1 . En cualquier caso, la soluci贸n debe estar en la rama v1 y debemos marcar esto como una nueva versi贸n.


 $ git commit -m "Emphasize our friendliness" testmod.go $ git tag v1.0.1 $ git push --tags origin v1 

Actualizaci贸n de m贸dulos


Por defecto, Go no actualiza los m贸dulos sin demanda. "Y eso es bueno", ya que a todos nos gustar铆a la previsibilidad en nuestras compilaciones. Si los m贸dulos Go se actualizan autom谩ticamente cada vez que se lanza una nueva versi贸n, volver铆amos a la "edad oscura anterior a Go1.11". Pero no, debemos decirle a Go que actualice los m贸dulos por nosotros.


Y lo haremos con la ayuda de nuestro viejo amigo: go get :


  • ejecute go get -u para usar la 煤ltima versi贸n menor o parche (es decir, el comando se actualizar谩 de 1.0.0 a, por ejemplo, 1.0.1 o 1.1.0, si dicha versi贸n est谩 disponible)


  • ejecute go get -u=patch para usar la 煤ltima versi贸n del parche (es decir, el paquete se actualizar谩 a 1.0.1, pero no a 1.1.0)


  • ejecute go get package@version para actualizar a una versi贸n espec铆fica (por ejemplo, github.com/robteix/testmod@v1.0.1 )



No hay forma en esta lista de actualizar a la 煤ltima versi贸n principal . Hay una buena raz贸n para esto, como veremos pronto.


Como nuestro programa us贸 la versi贸n 1.0.0 de nuestro paquete y acabamos de crear la versi贸n 1.0.1, cualquiera de los siguientes comandos nos actualizar谩 a 1.0.1:


 $ go get -u $ go get -u=patch $ go get github.com/robteix/testmod@v1.0.1 

Despu茅s de comenzar (digamos go get -u ), nuestro go.mod ha cambiado:


 module mod require github.com/robteix/testmod v1.0.1 

Versi贸n mayor


Seg煤n la especificaci贸n de las versiones sem谩nticas, la versi贸n principal difiere de la versi贸n secundaria. Las versiones principales pueden romper la compatibilidad con versiones anteriores. Desde el punto de vista de los m贸dulos Go, la versi贸n principal es un paquete completamente diferente .


Puede sonar salvaje al principio, pero tiene sentido: dos versiones de la biblioteca que son incompatibles entre s铆 son dos bibliotecas diferentes.


Hagamos un cambio importante en nuestro paquete. Supongamos que, con el tiempo, nos qued贸 claro que nuestra API es demasiado simple, demasiado limitada para los casos de nuestros usuarios, por lo que debemos cambiar la funci贸n Hi() para aceptar el idioma de bienvenida como par谩metro:


 package testmod import ( "errors" "fmt" ) // Hi returns a friendly greeting in language lang func Hi(name, lang string) (string, error) { switch lang { case "en": return fmt.Sprintf("Hi, %s!", name), nil case "pt": return fmt.Sprintf("Oi, %s!", name), nil case "es": return fmt.Sprintf("隆Hola, %s!", name), nil case "fr": return fmt.Sprintf("Bonjour, %s!", name), nil default: return "", errors.New("unknown language") } } 

Los programas existentes que usan nuestra API se interrumpir谩n porque a) no pasan el lenguaje como par谩metro yb) no esperan un retorno de error. Nuestra nueva API ya no es compatible con la versi贸n 1.x, as铆 que cumpla con la versi贸n 2.0.0.


Mencion茅 anteriormente que algunas versiones tienen caracter铆sticas, y ahora este es el caso.
La versi贸n 2 o posterior deber铆a cambiar la ruta de importaci贸n. Ahora estas son bibliotecas diferentes.


Haremos esto agregando una nueva ruta versionada al nombre de nuestro m贸dulo.


 module github.com/robteix/testmod/v2 

Todo lo dem谩s es igual: empujar, poner una etiqueta de que es v2.0.0 (y opcionalmente sod una rama v2)


 $ git commit testmod.go -m "Change Hi to allow multilang" $ git checkout -b v2 # optional but recommended $ echo "module github.com/robteix/testmod/v2" > go.mod $ git commit go.mod -m "Bump version to v2" $ git tag v2.0.0 $ git push --tags origin v2 # or master if we don't have a branch 

Actualizaci贸n de la versi贸n principal


Aunque lanzamos una nueva versi贸n incompatible de nuestra biblioteca, los programas existentes no se rompieron , porque contin煤an usando la versi贸n 1.0.1.
go get -u no descargar谩 la versi贸n 2.0.0.


Pero en alg煤n momento, yo, como usuario de la biblioteca, podr铆a querer actualizar a la versi贸n 2.0.0, porque, por ejemplo, soy uno de esos usuarios que necesitan soporte para varios idiomas.


Para actualizar, necesito cambiar mi programa en consecuencia:


 package main import ( "fmt" "github.com/robteix/testmod/v2" ) func main() { g, err := testmod.Hi("Roberto", "pt") if err != nil { panic(err) } fmt.Println(g) } 

Ahora, cuando ejecuto go build , se "cierra" y descarga la versi贸n 2.0.0 para m铆. Tenga en cuenta que aunque la ruta de importaci贸n ahora termina en "v2", Go todav铆a se refiere al m贸dulo por su nombre real ("testmod").


Como dije, la versi贸n principal es en todos los sentidos un paquete diferente. Estos dos m贸dulos Go no est谩n conectados de ninguna manera. Esto significa que podemos tener dos versiones incompatibles en un binario:


 package main import ( "fmt" "github.com/robteix/testmod" testmodML "github.com/robteix/testmod/v2" ) func main() { fmt.Println(testmod.Hi("Roberto")) g, err := testmodML.Hi("Roberto", "pt") if err != nil { panic(err) } fmt.Println(g) } 

Y esto elimina el problema com煤n con la gesti贸n de dependencias cuando las dependencias dependen de diferentes versiones de la misma biblioteca.



Volvamos a la versi贸n anterior, que usa solo testmod 2.0.0; si verificamos el contenido de go.mod , notaremos algo:


 module mod require github.com/robteix/testmod v1.0.1 require github.com/robteix/testmod/v2 v2.0.0 

Por defecto, Go no elimina las dependencias de go.mod hasta que lo solicite. Si tiene dependencias que ya no son necesarias y desea limpiarlas, puede usar el nuevo comando tidy :


 $ go mod tidy 

Ahora solo tenemos las dependencias que realmente usamos.


Vending


Ir m贸dulos por defecto ignora el vendor/ directorio. La idea es deshacerse gradualmente de la venta 1 . Pero si a煤n queremos agregar las dependencias "separadas" a nuestro control de versiones, podemos hacer esto:


 $ go mod vendor 

El equipo crear谩 el directorio vendor/ en la ra铆z de nuestro proyecto, que contiene el c贸digo fuente de todas las dependencias.


Sin embargo, go build por defecto a煤n ignora el contenido de este directorio. Si desea recopilar dependencias del vendor/ directorio, debe solicitarlo expl铆citamente.


 $ go build -mod vendor 

Supongo que muchos desarrolladores que quieran usar la venta autom谩tica ejecutar谩n go build -mod vendor , como de costumbre, en sus m谩quinas y usar谩n -mod vendor en su CI.


Nuevamente, los m贸dulos Go se est谩n alejando de la idea de vender para usar proxies para m贸dulos para aquellos que no desean depender directamente de los servicios de control de versiones ascendentes.


Hay formas de garantizar que go red no est茅 disponible (por ejemplo, usando GOPROXY=off ), pero este es el tema del pr贸ximo art铆culo.


Conclusi贸n


El art铆culo puede parecer complicado para alguien, pero esto es porque trat茅 de explicar mucho de una vez. La realidad es que los m贸dulos Go son generalmente simples hoy en d铆a: nosotros, como de costumbre, importamos el paquete a nuestro c贸digo, y el equipo go hace el resto por nosotros. Las dependencias se cargan autom谩ticamente durante el ensamblaje.


Los m贸dulos tambi茅n eliminan la necesidad de $GOPATH , que era un obst谩culo para los nuevos desarrolladores de Go que ten铆an problemas para entender por qu茅 deber铆an poner algo en un directorio espec铆fico.


La venta (no oficial) ha quedado en desuso a favor del uso de un proxy. 1
Puedo hacer un art铆culo separado sobre proxies para los m贸dulos Go.


Notas:


1 Creo que esta es una expresi贸n demasiado fuerte y algunos pueden tener la impresi贸n de que se est谩n eliminando las ventas en este momento. Esto no es asi. La venta sigue funcionando, aunque de forma ligeramente diferente que antes. Aparentemente, existe el deseo de reemplazar la venta autom谩tica con algo mejor, por ejemplo, un proxy (no es un hecho). Hasta ahora, esto es simplemente la b煤squeda de una mejor soluci贸n. Las ventas no desaparecer谩n hasta que se encuentre un buen reemplazo (si lo hay).

Source: https://habr.com/ru/post/es421411/


All Articles