Modul Go secara Mendalam

go.mod, pengelolaan versi, dan jalur impor semantik.

Daftar isi

Setiap proyek Go modern berdiri di atas satu modul, yaitu kumpulan paket yang dikelola bersama dan dideklarasikan oleh berkas go.mod di akar proyek. Topik ini membahas anatomi go.mod, siklus pembuatan modul sampai biner, aturan pemilihan versi dependensi dengan minimal version selection (pemilihan versi minimum), serta perubahan jalur impor yang menyertai versi mayor kedua dan sesudahnya. Seluruh perintah dieksekusi pada folder kerja tetap di /tmp dengan modul-modul lokal, sehingga setiap keluaran dapat direproduksi tanpa koneksi jaringan.

Anatomi go.mod#

Berkas go.mod adalah satu-satunya tempat identitas modul dan kebutuhan dependensinya dinyatakan. Perangkat go membaca berkas ini pada setiap build, sehingga penulisan yang keliru menghentikan kompilasi lebih dahulu, bukan baru saat program berjalan. Contoh berikut memuat direktif yang paling sering ditemui.

text Contoh: go.mod modul dengan satu dependensi
module contoh/aplikasi

go 1.27.3

require contoh/pustaka v0.2.0

replace contoh/pustaka => ../pustaka

Baris module menyatakan module path, yaitu jalur modul yang menjadi awal seluruh jalur impor paket di dalamnya dan biasanya ditulis seperti alamat repositorinya. Baris go menyatakan versi fitur bahasa minimum yang dipakai berkas-berkas modul. Baris require mencatat dependensi beserta versi minimumnya mengikuti semantic versioning, yaitu penomoran vMAJOR.MINOR.PATCH untuk versi mayor, minor, dan perbaikan. Baris replace mengganti sumber sebuah dependensi, misalnya menuju folder lokal saat pengembangan, dan hanya berlaku pada modul utama yang sedang dibangun. Direktif exclude dan retract melengkapi daftar ini dengan menandai versi yang tidak boleh dipakai atau yang ditarik oleh penerbitnya.

Modul pertama dari nol#

Modul dibuat dengan go mod init yang menerima satu argumen berupa jalur modulnya. Perintah itu menulis go.mod berisi direktif module dan go, lalu proyek langsung dapat dibangun. Program yang dipakai sepanjang topik ini adalah berkas main.go berikut, yang dapat dijalankan dengan go run main.go tanpa diikat ke modul mana pun:

go main.go
// Program contoh untuk modul contoh/salam.
package main

import "fmt"

func main() {
	for _, nama := range []string{"ali", "budi", "citra"} {
		fmt.Printf("Halo, %s!\n", nama)
	}
}
text Keluaran
Halo, ali!
Halo, budi!
Halo, citra!

Program di atas hanya memakai pustaka standar, sehingga dapat dikompilasi tanpa go.mod sekalipun. Perbedaannya baru muncul ketika proyek perlu diberi identitas, dicatat versinya, dan dijadikan dependensi oleh pihak lain. Langkah berikut menyiapkan folder kerja di /tmp, menginisialisasi modul bernama contoh/salam pada folder kosong, lalu menyalin programnya ke sana:

bash
# siapkan folder kerja modul di /tmp
rm -rf /tmp/modul-salam
mkdir -p /tmp/modul-salam

# inisialisasi modul di folder kosong agar go.mod langsung bersih
(cd /tmp/modul-salam && go mod init contoh/salam)

# salin program dari folder topik ini ke folder kerja
cp main.go /tmp/modul-salam/
cd /tmp/modul-salam

# tampilkan isi go.mod yang baru dibuat
cat go.mod
text Keluaran
go: creating new go.mod: module contoh/salam
module contoh/salam

go 1.27.3

Berkas go.mod yang dihasilkan persis berisi dua direktif itu, tanpa satu baris pun yang perlu ditulis manual. Modul bernama contoh/salam kini menjadi unit yang utuh, sehingga go build pada akar modul menghasilkan satu biner bernama salam, yaitu elemen terakhir jalur modulnya:

bash
cd /tmp/modul-salam
# bangun biner dari paket utama modul
go build
# tampilkan modul yang sedang aktif
go list -m
# jalankan biner hasil build
./salam
# tampilkan isi folder kerja, biner ikut tercantum
ls
text Keluaran
contoh/salam
Halo, ali!
Halo, budi!
Halo, citra!
go.mod
main.go
salam

go list -m membaca go.mod dan mencetak jalur modul yang sedang aktif, sedangkan ls memperlihatkan biner baru di antara berkas kerja. Nama biner itu bukan kebetulan, karena elemen terakhir jalur modul yang menjadi identitas proyek sekaligus menjadi nama perintahnya di terminal.

Pemilihan versi dengan minimal version selection#

Kebutuhan pada require menyatakan versi minimum, bukan versi yang terkunci. Pemilih versi Go menerapkan minimal version selection, yaitu memilih versi tertinggi yang benar-benar diminta oleh sesuatu di dalam graf build, bukan versi terbaru yang tersedia di repositori. Akibatnya, versi baru yang diterbitkan sebuah dependensi tidak pernah masuk ke build secara diam-diam, dan graf yang sama selalu menghasilkan pilihan yang sama.

Pemilihan itu terlihat pada demo dua tingkat. Modul pustaka menyediakan fungsi sapaan, modul laporan memakai pustaka itu di dalamnya, sedangkan aplikasi memakai keduanya. Seluruh modul berada di folder lokal, dan replace menggantikan peran unduhan jaringan:

bash
rm -rf /tmp/modul-versi
mkdir -p /tmp/modul-versi/pustaka-v0.1.0 /tmp/modul-versi/laporan /tmp/modul-versi/aplikasi

# modul pustaka versi v0.1.0
(cd /tmp/modul-versi/pustaka-v0.1.0 && go mod init contoh/pustaka)
cat > /tmp/modul-versi/pustaka-v0.1.0/pustaka.go <<'EOF'
package pustaka

// Sapa menyapa satu nama dengan gaya versi awal.
func Sapa(nama string) string {
	return "Halo, " + nama + "!"
}
EOF

# modul laporan yang bergantung pada pustaka versi v0.1.0
(cd /tmp/modul-versi/laporan && go mod init contoh/laporan)
(cd /tmp/modul-versi/laporan && go mod edit -require=contoh/[email protected])
cat > /tmp/modul-versi/laporan/laporan.go <<'EOF'
package laporan

import "contoh/pustaka"

// Rekap menyusun satu baris rekap untuk satu nama.
func Rekap(nama string) string {
	return "rekap untuk " + pustaka.Sapa(nama)
}
EOF

# modul aplikasi yang memakai pustaka dan laporan sekaligus
(cd /tmp/modul-versi/aplikasi && go mod init contoh/aplikasi)
(cd /tmp/modul-versi/aplikasi && go mod edit -require=contoh/[email protected] -replace=contoh/pustaka=/tmp/modul-versi/pustaka-v0.1.0)
(cd /tmp/modul-versi/aplikasi && go mod edit -require=contoh/[email protected] -replace=contoh/laporan=/tmp/modul-versi/laporan)
cat > /tmp/modul-versi/aplikasi/main.go <<'EOF'
package main

import (
	"fmt"

	"contoh/laporan"
	"contoh/pustaka"
)

func main() {
	fmt.Println(pustaka.Sapa("budi"))
	fmt.Println(laporan.Rekap("citra"))
}
EOF

# jalankan aplikasi dengan graf dependensi yang baru tersusun
cd /tmp/modul-versi/aplikasi
go run .
text Keluaran
go: creating new go.mod: module contoh/pustaka
go: creating new go.mod: module contoh/laporan
go: creating new go.mod: module contoh/aplikasi
Halo, budi!
rekap untuk Halo, citra!

Selanjutnya pustaka diterbitkan versi v0.2.0 dengan isi yang berbeda, lalu aplikasi menaikkan kebutuhannya ke versi itu. Folder pustaka lama diganti folder baru, sehingga perubahan isi pustakanya terlihat pada keluaran program:

bash
# folder pustaka dengan isi versi v0.2.0
mkdir -p /tmp/modul-versi/pustaka-v0.2.0
(cd /tmp/modul-versi/pustaka-v0.2.0 && go mod init contoh/pustaka)
cat > /tmp/modul-versi/pustaka-v0.2.0/pustaka.go <<'EOF'
package pustaka

// Sapa menyapa satu nama dengan gaya versi baru.
func Sapa(nama string) string {
	return "Selamat datang, " + nama + "!"
}
EOF

# naikkan kebutuhan pustaka dan arahkan replace ke folder versi baru
cd /tmp/modul-versi/aplikasi
go mod edit -require=contoh/[email protected] -replace=contoh/pustaka=/tmp/modul-versi/pustaka-v0.2.0

# seluruh pemakai pustaka kini memakai versi yang sama
go run .
go list -m all
text Keluaran
go: creating new go.mod: module contoh/pustaka
Selamat datang, budi!
rekap untuk Selamat datang, citra!
contoh/aplikasi
contoh/laporan v0.1.0 => /tmp/modul-versi/laporan
contoh/pustaka v0.2.0 => /tmp/modul-versi/pustaka-v0.2.0

Dua baris terakhir dari go list -m all memperlihatkan hasil pemilihannya. Modul laporan hanya meminta pustaka v0.1.0 di go.mod miliknya, tetapi karena aplikasi meminta v0.2.0, versi tertinggi yang diminta itu dipakai untuk seluruh graf, termasuk dari dalam laporan. Sebaliknya, tidak ada versi yang lebih baru diseret masuk karena tidak ada yang memintanya. Keluaran program mengonfirmasinya, yaitu rekap dari laporan ikut memakai gaya sapaan v0.2.0.

Pada proyek nyata, penaikan versi tidak dilakukan dengan go mod edit melainkan dengan go get, yang mengunduh modul dari repositorinya, memperbarui require, dan menuliskan checksum unduhan ke go.sum. Berkas go.sum itu ditulis otomatis oleh perangkat go dan berfungsi memverifikasi integritas setiap modul yang terunduh:

bash
# tidak-dijalankan: go get mengunduh dependensi dari jaringan
go get contoh/[email protected]
go mod tidy

Jalur impor untuk versi mayor#

Versi mayor adalah satu-satunya tingkatan yang boleh memecah kompatibilitas. Karena itu, Go menerapkan aturan khusus yang disebut semantic import versioning, yaitu jalur modul untuk versi mayor 2 ke atas harus berakhir dengan akhiran /N sesuai nomor mayornya. Modul pustaka versi kedua karena itu memiliki jalur modul contoh/pustaka/v2, dan seluruh paketnya diimpor melalui jalur yang menyertakan akhiran itu. Dua versi mayor menjadi dua modul berbeda yang dapat dipakai bersama pada satu program:

bash
# folder pustaka versi mayor kedua dengan jalur modul berakhiran /v2
mkdir -p /tmp/modul-versi/pustaka-v2
(cd /tmp/modul-versi/pustaka-v2 && go mod init contoh/pustaka/v2)
cat > /tmp/modul-versi/pustaka-v2/pustaka.go <<'EOF'
package pustaka

// Sapa menyapa satu nama dengan gaya versi mayor kedua.
func Sapa(nama string) string {
	return "Salam sejahtera, " + nama + "."
}
EOF

# tulis ulang main.go agar memakai dua versi mayor sekaligus
cat > /tmp/modul-versi/aplikasi/main.go <<'EOF'
package main

import (
	"fmt"

	"contoh/laporan"
	"contoh/pustaka"
	pustakav2 "contoh/pustaka/v2"
)

func main() {
	fmt.Println(pustaka.Sapa("budi"))
	fmt.Println(pustakav2.Sapa("budi"))
	fmt.Println(laporan.Rekap("citra"))
}
EOF

# catat ketergantungan v2 lalu jalankan programnya
cd /tmp/modul-versi/aplikasi
go mod edit -require=contoh/pustaka/[email protected] -replace=contoh/pustaka/v2=/tmp/modul-versi/pustaka-v2
go run .
cat go.mod
text Keluaran
go: creating new go.mod: module contoh/pustaka/v2
Selamat datang, budi!
Salam sejahtera, budi.
rekap untuk Selamat datang, citra!
module contoh/aplikasi

go 1.27.3

require (
	contoh/laporan v0.1.0
	contoh/pustaka v0.2.0
	contoh/pustaka/v2 v2.0.0
)

replace contoh/pustaka => /tmp/modul-versi/pustaka-v0.2.0

replace contoh/laporan => /tmp/modul-versi/laporan

replace contoh/pustaka/v2 => /tmp/modul-versi/pustaka-v2

Keluaran program kini memuat tiga baris, dan baris keduanya berasal dari modul contoh/pustaka/v2 yang berdampingan dengan contoh/pustaka pada graf yang sama. Di dalam go.mod, keduanya dicatat sebagai dua kebutuhan terpisah dengan nomor mayornya masing-masing. Impor v2 memakai alias pustakav2 karena kedua paket sama-sama bernama pustaka, dan tanpa alias kedua nama itu berbenturan pada satu berkas. Pemakai pustaka lama tidak perlu mengubah apa pun, karena jalur impornya masih menunjuk modul v1.

Kesalahan umum#

Tiga kekeliruan berikut paling sering muncul saat modul dikelola.

  1. Menulis require dengan versi yang belum tersedia tanpa replace. Perangkat go mencoba menemukan versi itu pada repositori modulnya saat build, lalu melaporkan bahwa versi tidak ditemukan. Kebutuhan versi hanya aman ditulis manual bila sumbernya diganti dengan replace, sedangkan penaikan versi normal dikerjakan go get.
  2. Menyunting go.sum secara manual. Berkas go.sum menyimpan checksum modul yang diunduh dan seluruh isinya ditulis perangkat go, misalnya oleh go get dan go mod tidy. Suntingan manual merusak verifikasi integritas, dan perbaikannya cukup menghapus go.sum lalu membiarkan perangkat go menuliskannya kembali.
  3. Menambahkan akhiran /v2 pada jalur impor tanpa mengubah jalur modul. Jalur pada direktif module dan jalur impor harus sama persis, sehingga impor contoh/pustaka/v2 terhadap modul yang masih bernama contoh/pustaka ditolak saat build. Perbaikannya, perbarui direktif module bersamaan dengan seluruh jalur impor yang menyertainya.

Ringkasan#

  • go.mod menyatakan jalur modul, versi bahasa, dan kebutuhan dependensi, sedangkan replace mengganti sumber dependensi pada pengembangan lokal.
  • Pemilihan versi memakai aturan minimum, yaitu versi tertinggi yang diminta graf build, sehingga versi baru tidak pernah masuk tanpa diminta.
  • go list -m all menampilkan modul terpilih beserta penggantinya, sedangkan go get berperan menaikkan versi pada proyek nyata.
  • Versi mayor 2 ke atas memakai jalur modul berakhiran /N, sehingga dua versi mayor dapat diimpor bersama melalui dua jalur berbeda.

Referensi#