Go Module dan Workspace
Definisi
Di Go, satuan organisasi kode tidak cuma package. Ada beberapa lapis yang saling terkait:
- package: unit kompilasi dan namespace pada level source file.
- module: unit versioning dan distribusi yang dikelola lewat
go.mod. - workspace: penghubung beberapa module lokal lewat
go.work. - dependency management: cara Go memilih, mengunduh, dan mengunci versi dependency.
Kalau dipakai dengan sehat, empat lapis ini bikin codebase tetap mudah dinavigasi, build tetap deterministik, dan import path tidak berantakan.
Kapan topik ini penting
Topik ini relevan saat kamu mulai melihat tanda-tanda berikut:
- package sudah banyak, tapi struktur folder terasa campur aduk
- perlu memisahkan kode reusable dari kode aplikasi
- ada lebih dari satu module dalam satu repo
- build lokal beda hasil dengan CI karena versi dependency berubah
- import path mulai “aneh” karena rename folder atau pindah module
Package di Go: unit kompilasi dan boundary logika
Package adalah cara Go mengelompokkan file .go yang dikompilasi bersama.
Aturan dasar
- Semua file dalam folder yang sama umumnya berada di package yang sama.
- Nama package bukan harus sama dengan nama folder, walau biasanya sebaiknya konsisten.
- File dalam package yang sama saling mengakses identifier tanpa import.
- Identifier yang diawali huruf kapital diekspor keluar package.
Struktur package yang sehat
Struktur yang sehat bukan yang paling dalam, tapi yang paling mudah dipahami:
- 1 folder = 1 tanggung jawab utama
- package kecil dan fokus
- dependency mengalir ke satu arah, bukan saling silang
- nama package singkat, deskriptif, dan domain-oriented
Contoh struktur yang lebih sehat:
myapp/
go.mod
cmd/
api/
main.go
internal/
auth/
service.go
token.go
billing/
service.go
storage/
postgres/
repo.go
pkg/
timeutil/
timeutil.goPrinsip praktis
cmd/...untuk entrypoint aplikasi.internal/...untuk logic yang hanya boleh dipakai oleh module itu sendiri.pkg/...hanya kalau kamu memang ingin membentuk library publik yang stabil.- Hindari package yang terlalu generik seperti
utils,common, atauhelperskalau isinya mulai membesar dan bercampur.
Tanda package mulai sakit
- satu package punya banyak alasan untuk berubah
- nama package tidak menjelaskan domain
- package diimpor ke banyak tempat, tapi isinya saling tergantung
- fungsi-fungsi kecil berakhir jadi “gudang” tanpa batas jelas
Kalau terjadi itu, pecah berdasarkan domain atau alur kerja, bukan berdasarkan jenis fungsi semata.
Kapan pakai internal package
Folder internal dipakai untuk membatasi visibility di level import path.
Aturan internal
Kode di bawah folder internal hanya bisa di-import oleh package yang berada di dalam parent tree yang sama.
Contoh:
myapp/
internal/
auth/Package lain di dalam myapp/... bisa import myapp/internal/auth, tapi kode di luar tree module itu tidak bisa.
Kapan internal tepat dipakai
Pakai internal untuk:
- domain logic yang bukan API publik
- helper yang hanya relevan untuk satu aplikasi
- adapter ke database, queue, cache, atau external service
- code yang belum siap dijadikan kontrak publik
Kapan jangan pakai internal
Jangan pakai internal kalau:
- kamu sedang membuat library yang memang ingin dipakai oleh project lain
- boundary kode sudah jelas publik dan stabil
- kamu hanya memakai
internalsebagai “tempat buang” karena belum sempat menamai package dengan benar
internal bagus untuk kontrol boundary, tapi bukan pengganti desain package yang rapi.
Module: go.mod sebagai sumber identitas dan versi
Satu module biasanya dimulai dari file go.mod.
Isi penting go.mod
Contoh sederhana:
module example.com/myapp
go 1.22
require (
github.com/google/uuid v1.6.0
)Bagian paling penting:
module: import path utama module.go: versi bahasa Go yang jadi baseline untuk module itu.require: dependency langsung yang dipakai.replacedanexclude: override atau menolak versi tertentu saat diperlukan.
Cara Go membaca go.mod
Go memakai go.mod untuk menentukan:
- identitas module
- dependency langsung
- versi dependency yang diselesaikan oleh MVS
- aturan replace/exclude lokal
Yang sering dilupakan: module di go.mod harus cocok dengan import path yang dipakai orang untuk mengimpor package dari module itu.
Kalau tidak cocok, import path akan terasa “benar di folder, salah di resolusi”.
go.sum: jejak integritas dependency
go.sum menyimpan checksum untuk memastikan dependency yang diunduh tetap sama dengan yang sudah pernah diverifikasi.
Fungsi utama go.sum
- mencegah supply-chain drift
- memastikan build berikutnya memakai artifact yang sama
- membantu Go memverifikasi modul yang diunduh dari proxy atau VCS
Kenapa file ini penting
Kalau go.mod menentukan versi, go.sum membantu memastikan isi versi itu konsisten.
Secara praktis:
go.mod= daftar dan versigo.sum= bukti integritas isi dependency
Catatan kerja harian
- Jangan edit
go.summanual. - Kalau dependency berubah, biarkan
goyang memperbarui file ini. - Perubahan
go.sumkecil pun bisa berarti graph dependency berubah.
Cara kerja resolusi dependency
Go memakai pendekatan yang cenderung deterministik: version selection mengutamakan versi tertinggi yang dibutuhkan dalam graph, dengan beberapa aturan tambahan.
Alur sederhananya
Inti yang perlu diingat
- Go tidak memilih versi “terbaru secara global”, tapi versi minimal yang memenuhi semua requirement dalam graph, lalu menstabilkannya lewat aturan MVS.
- Kalau dua dependency minta versi berbeda, Go akan memilih versi yang kompatibel menurut resolusi graph.
replacebisa mengubah sumber module untuk local development atau patch sementara.
Kapan perlu workspaces (go.work)
Workspace dipakai saat kamu ingin beberapa module lokal bekerja bersama tanpa harus merilis semuanya dulu.
Cocok untuk go.work
- monorepo berisi beberapa module
- kamu mengembangkan library dan aplikasi sekaligus
- ada service, shared library, dan tools di repo yang sama
- ingin
go testataugo runmemakai source lokal antar-module
Tidak perlu go.work kalau
- repo cuma punya satu module
- semua kode memang satu boundary deployment
- kebutuhan lokal bisa diselesaikan dengan
replacesementara
Bedanya go.work dan replace
replacemengubah dependency pada level satu module.go.workmenggabungkan beberapa module lokal pada level workspace.
go.work biasanya lebih bersih untuk dev experience di monorepo, karena tidak perlu memodifikasi go.mod hanya untuk pointer lokal.
Alur workspace vs module
Interpretasi praktis
- workspace memutuskan module lokal mana yang aktif
- masing-masing module tetap punya
go.modsendiri - dependency dan versioning tetap dihitung per module
Versioning dependency: aturan yang sering jadi jebakan
1) Major version >= 2 harus ada di import path
Ini salah satu jebakan paling umum.
Kalau module sudah v2, import path-nya harus membawa suffix /v2.
Contoh:
import "example.com/lib/v2"Bukan:
import "example.com/lib"Kalau tidak, Go akan menganggap itu module yang berbeda atau versi yang salah.
2) Rename repository sering memutus import path
Kalau repo pindah domain atau org, import path yang lama bisa tetap hidup sementara melalui replace, tapi jangan anggap itu solusi permanen.
Yang perlu dicek:
moduleline digo.mod- import path di seluruh codebase
- tag release yang sesuai
- dokumentasi pengguna library
3) Tag versi harus cocok dengan semantic versioning
Go mengandalkan tag seperti v1.2.3.
Jebakan umum:
- tag tidak memakai prefix
v - major version tag tidak cocok dengan path
/v2 - tag sudah dibuat, tapi
modulepath belum disesuaikan
4) Transitive dependency bisa naik sendiri
Kamu mungkin tidak mengubah dependency langsung, tapi dependency transitive bisa ikut naik karena requirement module lain.
Itulah kenapa build yang stabil perlu:
- review
go.mod - review
go.sum - cek
go list -m allsaat ada perilaku aneh
5) replace sering bikin build lokal berbeda dari CI
replace berguna, tapi juga berbahaya kalau dibiarkan terlalu lama.
Risiko:
- lokal pakai kode path A, CI pakai versi tag B
- bug muncul hanya setelah merge atau release
- dependency graph tidak merefleksikan release nyata
Kalau replace sudah tidak diperlukan, hapus.
Struktur import path yang sehat
Prinsip import path
Import path yang baik biasanya:
- stabil
- bisa ditebak dari repo/module root
- mencerminkan boundary pemakaian
- tidak bergantung pada folder sementara atau nama eksperimen
Contoh pola yang bagus
example.com/myapp/cmd/api
example.com/myapp/internal/auth
example.com/myapp/internal/storage/postgres
example.com/myapp/pkg/timeutilContoh pola yang biasanya berujung problem
example.com/myapp/misc
example.com/myapp/stuff
example.com/myapp/tempMasalahnya bukan pada namanya saja, tapi pada fakta bahwa folder seperti itu sering jadi tempat “sementara” yang tidak pernah sementara.
Praktik struktur project yang sehat
Untuk aplikasi
- simpan entrypoint di
cmd/ - simpan domain logic di
internal/ - simpan helper publik hanya jika memang reusable
- pisahkan adapter infrastruktur dari business logic
Untuk library
- jadikan root module sebagai API publik utama
- minimalkan surface area export
- pertahankan backward compatibility pada package publik
- pikirkan major version sejak awal kalau API akan berkembang
Untuk monorepo
- gunakan beberapa module jika lifecycle-nya berbeda
- gunakan
go.workuntuk development lokal - hindari coupling import yang memaksa semua subproject rilis bersamaan
Debugging cepat saat dependency terasa aneh
Checklist yang biasanya paling cepat membantu:
- Cek
go.modroot module dan import path. - Cek apakah ada
replaceyang tidak sengaja masih aktif. - Jalankan
go list -m alluntuk melihat graph versi yang benar-benar terpakai. - Cek apakah major version module sudah konsisten dengan import path.
- Cek
go.sumkalau ada checksum mismatch atau module cache yang tampak tidak wajar. - Kalau monorepo, pastikan workspace aktif sesuai harapan.
Ringkasan keputusan
- Pakai package untuk boundary kode yang kecil dan jelas.
- Pakai
internaluntuk kode yang tidak boleh jadi API publik. - Pakai module untuk versioning dan distribusi.
- Pakai
go.worksaat beberapa module lokal perlu bekerja bersama. - Waspadai major version path, replace, dan rename import path.
Referensi terkait
go help modulesgo help work- dokumentasi resmi Go Modules dan Workspaces
- artikel ini cocok dibaca berdampingan dengan topik package, project structure, dan dependency hygiene di Go