Ketika module dipublikasikan, semua package di dalamnya secara default dapat diimpor siapa saja — termasuk package yang sebenarnya hanya menjadi detail implementasi. Mengubah detail semacam itu lalu berpotensi merusak proyek orang lain yang tidak kita ketahui. Go menjawab kebutuhan ini dengan aturan direktori internal/ yang ditegakkan langsung oleh compiler: package di dalamnya hanya boleh diimpor oleh kode pada pohon direktori induknya. Pada chapter ini kita akan membedah aturannya, membuktikan dua galat pelanggaran secara nyata, lalu membandingkannya dengan mekanisme unexported dari chapter sebelumnya.
Aturan Pohon internal #
Aturan lengkapnya ringkas: import path yang memuat segmen internal hanya sah bila kode pengimpor berada di dalam subtree direktori induk dari internal tersebut. Ambil struktur berikut dari bundle chapter ini (module contoh.com/gudang):
src/
├── go.mod ← module contoh.com/gudang
├── main.go
├── internal/
│ └── stok/ ← boleh diimpor oleh SEMUA package di gudang
└── layanan/
├── layanan.go
└── internal/
└── pajak/ ← hanya boleh diimpor oleh package di bawah layanan/Direktori internal/ pertama berada langsung di bawah akar module, jadi induknya adalah seluruh module — semua package contoh.com/gudang/... boleh mengimpornya. Direktori internal/ kedua berada di bawah layanan/, jadi induknya hanya contoh.com/gudang/layanan/... — kode lain, termasuk main.go di akar, ditolak. Semakin dalam internal diletakkan, semakin sempit lingkup pemuji yang diizinkan.
Demo: Import yang Sah #
Program utama module demo memanggil kedua batas tersebut dengan benar — internal/stok diimpor dari akar, sementara layanan/internal/pajak hanya diimpor dari dalam layanan:
package main
import (
"fmt"
"contoh.com/gudang/internal/stok"
"contoh.com/gudang/layanan"
)
func main() {
nilai := stok.Total(25000, 10)
fmt.Println("stok :", nilai)
layanan.Tagihan(nilai)
}Package layanan menjadi jembatan satu-satunya menuju pajak:
package layanan
import (
"fmt"
"contoh.com/gudang/layanan/internal/pajak"
)
// Tagihan mencetak nilai dasar beserta pajaknya.
func Tagihan(dasar int) {
fmt.Println("tagihan:", dasar, "pajak:", pajak.Hitung(dasar))
}Jalankan dari dalam direktori src/:
cd src && go run .Output yang Dihasilkan:
stok : 250000
tagihan: 250000 pajak: 25000Mari kita telaah alur kedua berkas di atas:
main.gobaris 3-8 (blokimport): mengimporinternal/stok— sah karenamain.goberada dalam pohon indukinternal/tersebut (akar module).main.gobaris 11 (stok.Total): memanggil fungsi internal module lewat pintu yang memang dibuka untuk seluruh module.main.gobaris 13 (layanan.Tagihan): berinteraksi denganpajaksecara tidak langsung —main.gosama sekali tidak menyebutpajak, sebab akses langsung akan gagal dikompilasi.layanan.gobaris 3-7:layananberada di bawahlayanan/sendiri, sehingga mengimporlayanan/internal/pajakdiperbolehkan — inilah posisi induk yang dimaksud aturan.
Galat Nyata: Melanggar Batas internal #
Kedua pelanggaran berikut dijalankan sungguhan pada salinan module demo. Pertama, main.go dipindah ke akar mencoba mengimpor pajak yang bukan wilayahnya:
import "contoh.com/gudang/layanan/internal/pajak"Output yang Dihasilkan:
package contoh.com/gudang
main.go:7:2: use of internal package contoh.com/gudang/layanan/internal/pajak not allowedKedua, module lain (dengan replace ke direktori demo) mencoba mengimpor contoh.com/gudang/internal/stok dari luar module gudang:
Output yang Dihasilkan:
package contoh.com/konsumen
main.go:6:2: use of internal package contoh.com/gudang/internal/stok not allowedBentuk pesannya sama: use of internal package ... not allowed, diikuti posisi berkas pengimpor. Perhatikan kasus kedua — meskipun internal/ pertama tadi “dibuka untuk seluruh module”, pintu itu tertutup rapat untuk module lain: contoh.com/konsumen berada di luar pohon contoh.com/gudang sama sekali.
internal atau unexported? #
| Mekanisme | Tingkat | Melindungi |
|---|---|---|
internal/ | Antar package | Seluruh package dari pemanggil yang tidak berhak |
| Unexported (huruf kecil) | Antar package juga | Identifier individual di dalam satu package |
Keduanya saling melengkapi: internal/ menyembunyikan package utuh — API privat, helper domain, kode hasil generate — sedangkan unexported menyembunyikan anggota di dalam package yang memang dibuka. Kombinasi umumnya: taruh detail di internal/, lalu di package pintu masuk hanya ekspor identifier yang benar-benar stabil.
Dengan internal/, arsitektur kita kini punya pagar antar wilayah. Persoalan berikutnya lebih ringan namun keseharian: import path yang panjang dan bentrok nama package. Solusinya — alias — kita bahas pada chapter Package Alias
.
Referensi #
- Go Modules Reference: internal packages — aturan resmi direktori internal.
- Organizing a Go module — penempatan internal dalam layout module.
Berkas Sumber
Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.
src/internal/stok/stok.go
Unduh berkaspackage stok
// Total menghitung nilai stok berdasarkan harga satuan.
func Total(hargaSatuan, jumlah int) int {
return hargaSatuan * jumlah
}
src/layanan/internal/pajak/pajak.go
Unduh berkaspackage pajak
// Hitung menghitung pajak atas nilai dasar.
func Hitung(dasar int) int {
return dasar / 10
}
src/layanan/layanan.go
Unduh berkaspackage layanan
import (
"fmt"
"contoh.com/gudang/layanan/internal/pajak"
)
// Tagihan mencetak nilai dasar beserta pajaknya.
func Tagihan(dasar int) {
fmt.Println("tagihan:", dasar, "pajak:", pajak.Hitung(dasar))
}
src/main.go
Unduh berkaspackage main
import (
"fmt"
"contoh.com/gudang/internal/stok"
"contoh.com/gudang/layanan"
)
func main() {
nilai := stok.Total(25000, 10)
fmt.Println("stok :", nilai)
layanan.Tagihan(nilai)
}