Mòduls a Go: què són, com funcionen i per què ja no hauries d'aprendre GOPATH
Guia pràctica de go modules: go.mod, go.sum, dependències, versionat i errors habituals. Oblida GOPATH.

Molts tutorials de Go segueixen explicant GOPATH com si fos el 2017. No ho facis. Des de Go 1.16, els mòduls són el sistema per defecte per gestionar dependències, i des de Go 1.22 GOPATH com a workspace de desenvolupament és bàsicament un artefacte històric. Si estàs aprenent Go ara, aprendre amb GOPATH com a centre és aprendre amb una foto vella. Els mòduls resolen el problema de dependències de forma explícita, reproduïble i sense màgia de directoris.
Si véns d’altres ecosistemes, pensa en go.mod com el teu package.json, Cargo.toml o build.gradle.kts, però més simple i sense un gestor de paquets separat. Tot està integrat en el propi comandament go.
Si encara estàs decidint si Go val el teu temps, fes una ullada a com començar amb Go abans de continuar aquí.
Què és un mòdul a Go
Un mòdul és una col·lecció de paquets Go que es versionen i distribueixen junts. A la pràctica, un mòdul és un directori que conté un arxiu go.mod a l’arrel. Aquest arxiu defineix tres coses fonamentals:
- El module path: la identitat del mòdul, que també és la ruta base per importar els seus paquets.
- La versió de Go mínima que necessita.
- Les dependències: quins altres mòduls necessita i en quina versió exacta.
Cada repositori sol contenir un sol mòdul. Pots tenir-ne diversos, però no ho facis tret que tinguis una raó molt clara (monorepos amb components independents, per exemple).
La diferència clau amb GOPATH: abans, tot el teu codi i totes les teves dependències vivien en un únic arbre de directoris sota $GOPATH/src. No hi havia versionat real, no hi havia reproduïbilitat, i dos projectes que necessitessin versions diferents de la mateixa llibreria… mala sort. Els mòduls eliminen tot això.
Anatomia de go.mod
Un go.mod típic té aquest aspecte:
module github.com/el-teu-usuari/el-meu-projecte
go 1.22
require (
github.com/gin-gonic/gin v1.10.0
github.com/jackc/pgx/v5 v5.6.0
go.uber.org/zap v1.27.0
)
require (
// dependències indirectes
github.com/bytedance/sonic v1.11.8 // indirect
github.com/gabriel-vasile/mimetype v1.4.4 // indirect
// ... més dependències indirectes
)Desglossament:
module github.com/el-teu-usuari/el-meu-projecte: el path del mòdul. Si el teu codi serà importat per altres, ha de coincidir amb la URL del repositori. Si és un binari que ningú importarà, pots posar el que vulguis, però la convenció segueix sent usar la URL del repo.go 1.22: la versió mínima de Go. No és decorativa. Go usa aquesta directiva per decidir quines funcionalitats del llenguatge estan disponibles i com es resolen les dependències.require: les dependències directes i indirectes. Go separa les directes de les indirectes amb un comentari// indirect.
Les dependències indirectes són mòduls que les teves dependències directes necessiten, però que tu no importes directament. Go les registra a go.mod per garantir builds reproduïbles.
Crear el teu primer mòdul: go mod init
Crear un mòdul és un sol comandament:
mkdir el-meu-projecte
cd el-meu-projecte
go mod init github.com/el-teu-usuari/el-meu-projecteAixò genera un go.mod mínim:
module github.com/el-teu-usuari/el-meu-projecte
go 1.22A partir d’aquí ja pots crear arxius .go, importar paquets estàndard i compilar. Quan afegeixis un import d’un paquet extern, Go descarregarà la dependència automàticament en compilar o en executar go mod tidy.
Un detall que confon gent que ve de Node o Python: no necessites un comandament tipus npm install o pip install abans d’escriure codi. Escrius l’import, executes go build o go run, i Go resol la dependència. Així de directe.
package main
import (
\"fmt\"
\"github.com/gin-gonic/gin\"
)
func main() {
r := gin.Default()
r.GET(\"/ping\", func(c *gin.Context) {
c.JSON(200, gin.H{\"message\": \"pong\"})
})
fmt.Println(\"Servidor arrencant a :8080\")
r.Run()
}En executar go run . per primera vegada, Go descarrega Gin i totes les seves dependències transitives, actualitza go.mod i genera go.sum.
Si necessites una visió més àmplia de com organitzar el codi dins del mòdul, mira estructura de projecte.
Afegir dependències: go get i go mod tidy
Hi ha dues formes principals d’afegir dependències.
go get
go get descarrega un mòdul i l’afegeix al teu go.mod:
go get github.com/jackc/pgx/v5@latestPots especificar versions concretes:
go get github.com/jackc/pgx/v5@v5.6.0
go get github.com/jackc/pgx/v5@v5.5.0 // downgrade
go get github.com/jackc/pgx/v5@abc1234 // commit específicDes de Go 1.18, go get ja no compila ni instal·la binaris. Només modifica go.mod i go.sum. Si vols instal·lar una eina CLI, usa go install:
go install golang.org/x/tools/gopls@latestAquesta distinció és important. go get gestiona dependències del teu projecte. go install instal·la executables.
go mod tidy
go mod tidy és el comandament que més usaràs en el dia a dia. Fa dues coses:
- Afegeix les dependències que el teu codi importa però que no estan a
go.mod. - Elimina les dependències que estan a
go.modperò que el teu codi ja no usa.
go mod tidyLa meva recomanació: executa’l sempre abans de fer commit. És la forma més fiable de mantenir go.mod net i sincronitzat amb el teu codi real. No et fiïs que go build ja ho hagi fet tot; go build afegeix dependències que falten, però no elimina les que sobren.
go.sum: què és i per què no hauries d’afegir-lo a .gitignore
Quan Go descarrega un mòdul, calcula un hash criptogràfic del seu contingut i el guarda a go.sum. L’arxiu té aquest aspecte:
github.com/gin-gonic/gin v1.10.0 h1:ABC123...=
github.com/gin-gonic/gin v1.10.0/go.mod h1:DEF456...=Cada entrada té dos hashes: un pel contingut complet del mòdul i un altre pel seu go.mod. Això permet verificar la integritat de les dependències sense descarregar-les senceres.
La pregunta que tothom fa: “Si ja tinc les versions a go.mod, per què necessito go.sum?”
Perquè go.mod diu quina versió usar. go.sum verifica que aquella versió no ha estat manipulada. És una mesura de seguretat. Si algú compromet un repositori i publica una versió v1.10.0 amb contingut diferent, el hash no coincidirà i Go es negarà a compilar.
go.sum va al repositori. Sempre. No el posis a .gitignore. La seguretat de la teva cadena de dependències depèn d’ell. Go a més consulta la Go checksum database per validar hashes contra un registre públic, però el go.sum local és la teva primera línia de defensa.
Un error habitual: fer merge de branques i tenir conflictes a go.sum. La solució és senzilla: accepta qualsevol de les dues versions i executa go mod tidy. Go regenerarà els hashes correctes.
Versionat semàntic a Go: la convenció de l’import path
Go adopta versionat semàntic (semver), però li afegeix una regla pròpia que és única en l’ecosistema: a partir de v2, la versió major forma part de l’import path.
Això significa que:
import \"github.com/jackc/pgx\" // v0.x o v1.x
import \"github.com/jackc/pgx/v5\" // v5.xSón, als efectes del compilador, mòduls diferents. Pots importar ambdós en el mateix projecte sense conflicte. Això és intencionat: Go tracta un canvi de versió major com un mòdul nou, cosa que evita l’infern de dependències incompatibles.
Per als mantenidors de llibreries, això implica que en publicar v2 del teu mòdul, has de:
- Actualitzar el
modulepath ago.modper incloure/v2. - Actualitzar tots els imports interns.
- Crear el tag
v2.0.0.
// go.mod per a v2
module github.com/el-teu-usuari/la-meva-llibreria/v2
go 1.22És més feina que a npm o Cargo, on simplement canvies el número de versió. Però el resultat és que a Go és literalment impossible que una actualització de versió major trenqui el teu build sense que tu l’hagis importat explícitament. No existeix l’equivalent a npm install trencant-ho tot perquè una dependència transitiva va pujar de major.
Pre-release i pseudo-versions
Si necessites usar un commit que no té tag, Go genera una pseudo-versió:
require github.com/algo/algo v0.0.0-20240115140000-abc1234def56El format és vX.Y.Z-YYYYMMDDHHMMSS-commitHash. No l’escriguis a mà. Usa go get github.com/algo/algo@abc1234 i Go la genera per tu.
La directiva replace: reemplaçar dependències
replace és una de les eines més útils de go.mod i una de les pitjor documentades. Permet redirigir un mòdul a una altra ubicació o versió.
Desenvolupament local
El cas d’ús més comú és treballar amb un fork local o una llibreria que estàs modificant al mateix temps que el teu projecte:
module github.com/el-teu-usuari/el-meu-projecte
go 1.22
require github.com/el-teu-usuari/la-meva-llibreria v1.3.0
replace github.com/el-teu-usuari/la-meva-llibreria => ../la-meva-llibreriaAmb això, Go usa el codi local a ../la-meva-llibreria en lloc de descarregar la versió publicada. Perfecte per al desenvolupament, però mai publiquis un mòdul amb un replace apuntant a un path local. Només funciona a la teva màquina.
Forks i correccions
Si necessites usar un fork d’una dependència perquè té un bug corregit que encara no s’ha publicat:
replace github.com/llibreria-original/algo => github.com/el-teu-fork/algo v1.3.1-fixRetract: marcar versions com a defectuoses
Des de Go 1.16, els mantenidors poden marcar versions com a retracted al seu go.mod:
retract (
v1.2.0 // conté un bug crític al parser
[v1.3.0, v1.3.5] // rang de versions defectuoses
)go get evitarà automàticament versions retracted i t’avisarà si ja les estàs usant.
Mòduls privats: GOPRIVATE i repos empresarials
Per defecte, Go intenta resoldre mòduls a través del proxy públic (proxy.golang.org) i validar els seus hashes contra la checksum database (sum.golang.org). Això no funciona amb repositoris privats.
La variable d’entorn GOPRIVATE diu a Go quins mòduls ha de resoldre directament contra el repositori, saltant-se el proxy i la checksum database:
go env -w GOPRIVATE=\"github.com/la-teva-empresa/*,gitlab.empresa.com/*\"Per a patrons més complexos, hi ha dues variables addicionals:
GONOSUMCHECK: mòduls que no es validen contra la checksum database (però sí usen el proxy).GONOPROXY: mòduls que no passen pel proxy (però sí es validen contra la checksum database).
GOPRIVATE és equivalent a posar el mateix valor a ambdues.
Autenticació amb repos privats
Go usa Git sota el capó per clonar repositoris. Si el teu repo privat usa HTTPS, has de configurar Git perquè s’autentiqui:
// A ~/.gitconfig o ~/.config/git/config
[url \"https://oauth2:EL_TEU_TOKEN@gitlab.empresa.com/\"]
insteadOf = https://gitlab.empresa.com/Per a GitHub, una altra opció és configurar gh auth o usar un .netrc:
// ~/.netrc
machine github.com
login el-teu-usuari
password ghp_EL_TEU_TOKEN_PERSONALSi treballes amb SSH:
[url \"ssh://git@github.com/\"]
insteadOf = https://github.com/Un problema recurrent en CI/CD: el pipeline no té accés al repositori privat. La solució habitual és injectar un token com a variable d’entorn i configurar l’insteadOf en el pas de setup.
Errors habituals i com solucionar-los
Aquests són els errors amb mòduls que he vist més, tant en codi propi com aliè.
”cannot find module providing package X”
main.go:5:2: no required module provides package github.com/algo/algo; to add it:
go get github.com/algo/algoL’import existeix al teu codi però la dependència no està a go.mod. Solució:
go get github.com/algo/algo
// o millor:
go mod tidy“ambiguous import”
Ocorre quan dos mòduls exporten el mateix package path. Sol passar amb mòduls que han migrat a v2 però tens imports barrejats. Revisa els teus imports i assegura’t que tots apunten a la mateixa versió major.
”checksum mismatch”
verifying github.com/algo/algo@v1.2.0: checksum mismatchEl hash del mòdul descarregat no coincideix amb el registrat a go.sum. Causes possibles:
- Caché corrupta:
go clean -modcachei torna a descarregar. - El mòdul ha estat alterat: algú ha republicat una versió amb contingut diferent. Això no hauria de passar amb mòduls públics gràcies a la checksum database, però pot ocórrer amb mòduls privats.
- Conflicte de merge a go.sum: resol el conflicte i executa
go mod tidy.
”module declares its path as X but was required as Y”
El go.mod del mòdul que estàs important diu un module path diferent al que has usat al teu require o import. Això passa amb forks: clones un repo, canvies el codi, però no actualitzes la línia module del seu go.mod. El mòdul segueix dient que és github.com/original/repo, però tu l’importes com a github.com/el-teu-fork/repo. Usa replace per solucionar-ho.
”go.mod has post-v0 module path but no major version suffix”
El teu go.mod diu module github.com/algo/algo però el tag és v2.0.0 o superior. A partir de v2, el module path ha d’incloure /v2. És una regla que Go aplica estrictament.
go.sum out of date
go: updates to go.sum needed, disabled by -mod=readonlyApareix en CI quan algú ha fet canvis en dependències però ha oblidat executar go mod tidy abans del commit. Solució: executa go mod tidy localment i fes commit dels canvis a go.mod i go.sum.
go mod tidy, go mod vendor, go mod download
Tres subcomandaments que sonen semblants però fan coses diferents.
go mod tidy
Ja l’hem vist: sincronitza go.mod i go.sum amb els imports reals del teu codi. Afegeix el que falta, elimina el que sobra. És el comandament que més usaràs.
go mod tidyTé una opció útil: go mod tidy -v et mostra quins mòduls ha afegit o eliminat. Usa’l quan go mod tidy faci canvis inesperats i vulguis entendre per què.
go mod vendor
Copia totes les dependències a un directori vendor/ dins del teu projecte:
go mod vendorAmb vendor/, el teu projecte es pot compilar sense accés a internet i sense dependre de proxy.golang.org. És útil en:
- Entorns amb accés restringit a internet (air-gapped).
- Quan vols tenir control total sobre el codi de tercers.
- Alguns pipelines de CI on no vols descarregar dependències a cada build.
Per compilar usant el vendor directory:
go build -mod=vendor ./...Des de Go 1.22, si existeix un directori vendor/, Go l’usa automàticament. No necessites el flag -mod=vendor explícitament.
La meva opinió: vendor té sentit en projectes que despleguen en entorns restrictius o que necessiten auditories de seguretat sobre el codi de tercers. Per a la resta, el mòdul cache i el proxy són suficients. Un directori vendor/ amb milers d’arxius embruta el repositori i fa que els diffs siguin il·legibles quan actualitzes dependències.
go mod download
Descarrega totes les dependències a la caché local sense compilar res:
go mod downloadÉs especialment útil en Dockerfiles, on pots aprofitar la caché de capes:
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN go build -o /app ./cmd/serverEn copiar primer només go.mod i go.sum, Docker reutilitza la capa de go mod download mentre no canviïn les dependències. Això accelera dràsticament els builds quan només canvies codi font.
go mod graph i go mod why
Dos comandaments de diagnòstic que poca gent coneix però que són molt útils:
go mod graphImprimeix el graf complet de dependències en format text. És útil per entendre per què hi ha una dependència transitiva.
go mod why github.com/algo/algoEt diu exactament quina cadena d’imports provoca que aquell mòdul sigui una dependència. Quan veus un mòdul a go.mod que no reconèixes i vols saber qui el va portar, go mod why et dona la resposta.
El workspace mode: go.work
Des de Go 1.18, existeix un mode addicional: workspaces. Un arxiu go.work en un directori pare et permet treballar amb múltiples mòduls simultàniament sense usar replace:
go 1.22
use (
./el-meu-projecte
./la-meva-llibreria
)Amb això, si el-meu-projecte importa la-meva-llibreria, Go usa automàticament la còpia local. És més net que replace per al desenvolupament multimòdul, perquè no modifiques el go.mod de cap projecte.
go work init ./el-meu-projecte ./la-meva-llibreriaNo pugis go.work al repositori (tret que tot el teu equip treballi amb la mateixa estructura de directoris). go.work és una eina de desenvolupament local, com un .env. Afegeix-lo a .gitignore.
Per a més detalls sobre el comandament go i totes les seves opcions, revisa l’article dedicat.
Oblida GOPATH i no mires enrere
Si hi ha alguna cosa que m’hauria agradat dir-me a mi mateix fa uns anys és que deixés de lluitar amb GOPATH i $GOPATH/src/github.com/.... Els mòduls van canviar la forma de treballar amb dependències a Go des de l’arrel: go.mod defineix el teu mòdul de forma explícita, go.sum garanteix la integritat de tot el que descarregues, i go mod tidy s’encarrega de mantenir ambdós fitxers nets. No necessites res més.
La resta del sistema encaixa de forma natural un cop interiorítzes aquestes tres peces. go get per afegir o actualitzar dependències, go install per a binaris, replace per al desenvolupament local i forks, GOPRIVATE per a repos privats, i vendor/ només quan tens una raó concreta per usar-lo. Les versions v2+ amb el seu import path diferent semblen estranyes al principi, però quan entens que és un mecanisme per evitar incompatibilitats silencioses, deixa de molestar.
No és el sistema de dependències més flexible del món. Però aquesta rigidesa és precisament el que fa que funcioni: builds reproduïbles, sense sorpreses entre màquines, sense configuració màgica de directoris. Després d”haver treballat amb Maven, pip i npm, valoro molt que go mod tidy faci el correcte sense que hagi de pensar-hi.


