Module yang baru diinisialisasi hanya berisi go.mod dan satu berkas main.go — cukup untuk program kecil. Namun begitu jumlah package bertambah dari tiga menjadi sepuluh, penempatan berkas mulai menentukan kesehatan proyek: program mana yang bisa dieksekusi, kode mana yang boleh diimpor pihak luar, dan kode mana yang benar-benar privat. Komunitas Go menyepakati sebuah pola layout yang sederhana namun elastis, dibangun dari tiga direktori: cmd/, internal/, dan pkg/. Pada chapter ini kita akan menyusun layout tersebut dalam module demo dan mengujinya sampai tuntas.
Proyek Kecil: Cukup main.go di Akar #
Jangan menerapkan struktur berlapis untuk skrip atau program mainan. Module di bawah ini sudah sah dan idiomatik:
catatan/
├── go.mod ← module contoh.com/catatan
└── main.go ← package main, satu-satunya berkasAturan praktisnya: seluruh kode masih muat dibaca dalam satu tarikan napas dan tidak ada package lain yang mengimpor kode Anda, satu main.go di akar sudah memadai. Struktur bertingkat baru memberi nilai ketika module memiliki lebih dari satu program atau lebih dari satu package.
Layout Standar: cmd, internal, pkg #
Bundle chapter ini berisi module demo contoh.com/toko dengan tiga direktori kunci:
src/
├── go.mod ← module contoh.com/toko
├── cmd/
│ └── kasir/
│ └── main.go ← package main (program kasir)
├── internal/
│ └── harga/
│ └── harga.go ← logika bisnis, khusus module ini
└── pkg/
└── format/
└── format.go ← utilitas format, boleh dipakai pihak luarKetiga direktori tersebut punya peran yang saling melengkapi:
| Direktori | Isi | Peran |
|---|---|---|
cmd/namaapp/ | Package main | Satu subdirektori untuk setiap program yang dieksekusi |
internal/ | Package apa pun | Kode privat; import dari module lain ditolak compiler |
pkg/ | Package apa pun | Kode yang secara sengaja dibuka untuk pemakai luar |
Program cmd/kasir/main.go merangkai kedua package pendukung:
package main
import (
"fmt"
"contoh.com/toko/internal/harga"
"contoh.com/toko/pkg/format"
)
func main() {
items := []int{15000, 27500, 8000}
total := harga.Total(items)
fmt.Println("Jumlah item :", len(items))
fmt.Println("Total :", format.Rupiah(total))
}Sementara internal/harga/harga.go menjumlahkan daftar harga, dan pkg/format/format.go memformat angka menjadi teks rupiah:
package harga
// Total menjumlahkan seluruh harga item.
func Total(items []int) int {
total := 0
for _, h := range items {
total += h
}
return total
}package format
import "strconv"
// Rupiah memformat bilangan bulat menjadi teks rupiah berpemisah titik.
func Rupiah(n int) string {
s := strconv.Itoa(n)
if len(s) <= 3 {
return "Rp" + s
}
hasil := ""
for len(s) > 3 {
hasil = "." + s[len(s)-3:] + hasil
s = s[:len(s)-3]
}
return "Rp" + s + hasil
}Karena main.go tidak lagi berada di akar module, kita menjalankannya dengan path package, bukan titik:
cd src && go run ./cmd/kasirOutput yang Dihasilkan:
Jumlah item : 3
Total : Rp50.500Mari kita telaah baris demi baris berkas utama:
- Baris 1 (
package main): berkas ini adalah program yang dapat dieksekusi, berada di subdirektoricmd/kasir/. - Baris 3-8 (blok
import): mengimpor packageinternal/hargauntuk logika bisnis danpkg/formatuntuk format tampilan; import path lengkap dimulai dari module pathcontoh.com/toko. - Baris 11 (
items := ...): mendeklarasikan potongan harga tiga item sebagai data masukan. - Baris 12 (
harga.Total(items)): memanggil fungsi dari direktoriinternal/— kode privat yang tak terlihat dari luar module. - Baris 13-14 (
fmt.Println): mencetak ringkasan;format.Rupiahmengubah angka50500menjadi teksRp50.500.
Direktori cmd: Satu Subdirektori per Program #
Direktori cmd/ menampung seluruh titik masuk program. Setiap subdirektori di dalamnya memuat satu package main dengan nama yang mencerminkan biner yang dihasilkan: cmd/kasir menghasilkan biner kasir, cmd/migrasi menghasilkan migrasi. Nama subdirektori inilah yang menjadi nama berkas biner setelah go build ./cmd/kasir, sehingga penamaan yang deskriptif sekaligus singkat sangat berpengaruh pada pengalaman pengguna CLI Anda.
Direktori internal dan pkg #
Direktori internal/ menikmati perlindungan khusus dari compiler: package di dalamnya hanya boleh diimpor oleh kode pada pohon direktori induknya. Aturan lengkap dan demo galatnya akan kita bedah pada chapter Package internal
. Direktori pkg/ tidak punya perlakuan khusus dari compiler — fungsinya murni konvensi untuk memberi sinyal “kode di sini aman diimpor pihak luar”.
Memilih Layout Berdasarkan Ukuran Proyek #
| Ukuran proyek | Bentuk yang cocok |
|---|---|
| Skrip / latihan | main.go di akar module |
| Satu aplikasi, beberapa package | cmd/app/ + internal/ |
| Aplikasi + library publik | cmd/app/ + internal/ + pkg/ (atau package publik di akar) |
| Banyak aplikasi / monorepo | Banyak subdirektori cmd/, internal/ per domain, pertimbangkan go.work |
Pola cmd/ + internal/ menutup mayoritas kebutuhan proyek aplikasi; tambahkan pkg/ hanya ketika memang ada package yang sengaja dipublikasikan. Setelah kerangka direktori berdiri, pertanyaan berikutnya adalah bagaimana mengontrol kode mana di dalam package yang boleh diakses dari luar — jawabannya ada pada kapitalisasi huruf pertama identifier, yang kita bahas pada chapter Export dan Unexported
.
Referensi #
- Organizing a Go module — panduan resmi penataan module.
- Tutorial: Create a Go module — struktur dasar dari tutorial resmi.
- Go Modules Reference — rujukan model module.
Berkas Sumber
Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.
src/cmd/kasir/main.go
Unduh berkaspackage main
import (
"fmt"
"contoh.com/toko/internal/harga"
"contoh.com/toko/pkg/format"
)
func main() {
items := []int{15000, 27500, 8000}
total := harga.Total(items)
fmt.Println("Jumlah item :", len(items))
fmt.Println("Total :", format.Rupiah(total))
}
src/internal/harga/harga.go
Unduh berkaspackage harga
// Total menjumlahkan seluruh harga item.
func Total(items []int) int {
total := 0
for _, h := range items {
total += h
}
return total
}
src/pkg/format/format.go
Unduh berkaspackage format
import "strconv"
// Rupiah memformat bilangan bulat menjadi teks rupiah berpemisah titik.
func Rupiah(n int) string {
s := strconv.Itoa(n)
if len(s) <= 3 {
return "Rp" + s
}
hasil := ""
for len(s) > 3 {
hasil = "." + s[len(s)-3:] + hasil
s = s[:len(s)-3]
}
return "Rp" + s + hasil
}