Bab 7 dari 10
Package, Struktur Proyek, dan Dependensi
Tujuan Pembelajaran
Setelah menyelesaikan bab ini, Anda mampu:
- membagi program menjadi package yang kohesif;
- membuat dan merawat module Go;
- memilih struktur proyek yang sederhana dan idiomatis;
- mengelola dependensi dengan
go mod; - memahami aturan ekspor, import, dan package
internal.
1. Package dan Module
Package adalah unit organisasi, kompilasi, dan akses kode. Module adalah kumpulan package berversi yang memiliki berkas go.mod.
Buat proyek baru:
mkdir taskcli
cd taskcli
go mod init example.com/taskcli
Nama module biasanya URL repositori tempat kode akan diterbitkan. Untuk eksperimen lokal, nama seperti example.com/taskcli sah selama import di proyek konsisten.
Setiap direktori normal berisi satu package. Nama package singkat, huruf kecil, dan tidak perlu mengulang konteks: gunakan task, bukan taskutils atau task_package.
2. Ekspor dan Import
Identifier berawalan huruf besar diekspor dari package; identifier berawalan huruf kecil hanya dapat digunakan di package yang sama.
// task/task.go
package task
import (
"errors"
"strings"
)
type Task struct {
ID int
Title string
}
func New(id int, title string) (Task, error) {
title = strings.TrimSpace(title)
if id < 1 {
return Task{}, errors.New("ID harus positif")
}
if title == "" {
return Task{}, errors.New("judul tidak boleh kosong")
}
return Task{ID: id, Title: title}, nil
}
// cmd/taskcli/main.go
package main
import (
"fmt"
"log"
"example.com/taskcli/task"
)
func main() {
t, err := task.New(1, "Belajar package")
if err != nil {
log.Fatal(err)
}
fmt.Println(t.Title)
}
Import dikelompokkan oleh gofmt: pustaka standar, baris kosong, lalu package module atau pihak ketiga. Hindari dot import karena asal identifier menjadi tidak jelas.
3. Struktur Proyek yang Proporsional
Untuk aplikasi kecil, satu main.go dapat cukup. Pecah direktori setelah ada tanggung jawab nyata yang berbeda.
taskcli/
├── go.mod
├── cmd/
│ └── taskcli/
│ └── main.go
├── internal/
│ └── task/
│ ├── task.go
│ └── store.go
└── README.md
Direktori cmd/taskcli berisi entry point. internal/task menyimpan kode aplikasi yang tidak boleh diimpor module lain. Go menegakkan batas internal saat kompilasi.
Jangan menyalin struktur proyek besar tanpa kebutuhan. Folder generik seperti utils, common, atau helpers sering menyembunyikan tanggung jawab yang tidak jelas.
4. Siklus Import
Go melarang import cycle. Jika package task mengimpor storage, maka storage tidak boleh kembali mengimpor task.
Solusi yang lazim:
- pindahkan tipe domain ke package yang paling memilikinya;
- definisikan interface kecil di package pemakai;
- gabungkan package jika pemisahannya tidak memberi manfaat;
- pindahkan kode bersama ke package ketiga hanya jika konsepnya benar-benar mandiri.
5. Mengelola Dependensi
go.mod mencatat path module, versi Go, dan kebutuhan module lain. go.sum mencatat checksum untuk verifikasi unduhan.
go mod tidy
go list -m all
go mod graph
go mod verify
go mod tidy menambah dependensi yang diperlukan dan menghapus yang tidak dipakai. Jalankan setelah perubahan import.
Jika memang memerlukan package eksternal:
go get example.com/some/module@v1.2.3
go mod tidy
Gunakan versi eksplisit bila proyek menuntut reproduksibilitas. Evaluasi lisensi, pemeliharaan, keamanan, dan ukuran transitive dependency sebelum menambah library. Pustaka standar sering sudah cukup.
Untuk memperbarui dependensi langsung dan tidak langsung sesuai batas kompatibilitas:
go get -u ./...
go mod tidy
go test ./...
Tinjau perubahan go.mod dan go.sum; jangan memperbarui tanpa menjalankan pengujian.
6. Package Documentation dan API
Dokumentasi identifier yang diekspor dimulai dengan namanya.
// Package task menyediakan model dan operasi tugas.
package task
// Task merepresentasikan satu pekerjaan yang dapat diselesaikan.
type Task struct {
ID int
}
API package yang baik memiliki permukaan kecil, nama jelas, dan aturan error yang konsisten. Hindari mengekspos representasi internal jika pengguna hanya memerlukan operasi tingkat tinggi.
7. Contoh Proyek Minimal yang Dapat Dikompilasi
hello/
├── go.mod
├── main.go
└── greeting/
└── greeting.go
// greeting/greeting.go
package greeting
import "strings"
func Hello(name string) string {
name = strings.TrimSpace(name)
if name == "" {
name = "dunia"
}
return "Halo, " + name + "!"
}
// main.go
package main
import (
"fmt"
"example.com/hello/greeting"
)
func main() {
fmt.Println(greeting.Hello("Gopher"))
}
go mod init example.com/hello
gofmt -w .
go run .
go test ./...
Kesalahan Umum
- Terlalu dini membuat banyak package. Mulai sederhana dan pisahkan saat tanggung jawab jelas.
- Nama package mengulang nama tipe.
task.Tasklebih baik daripadataskmodel.TaskModel. - Mengedit
go.sumsecara manual. Biarkan tool Go mengelolanya. - Mengabaikan
go mod tidy. Module dapat menyimpan kebutuhan usang atau kehilangan dependensi. - Memakai
replacelokal lalu lupa menghapusnya. Build pengguna lain akan gagal. - Menciptakan import cycle. Evaluasi kembali arah dependensi atau batas package.
- Menambah library untuk fungsi kecil. Periksa pustaka standar terlebih dahulu.
Latihan
- Buat module
example.com/librarydengan packagebookdan program dicmd/catalog. - Pindahkan package
bookkeinternal/book, lalu coba impor dari module lain dan amati error. - Jalankan
go list ./...,go mod tidy, dango mod verify; jelaskan fungsi tiap perintah. - Temukan package bernama
utilspada proyek latihan dan ubah menjadi package dengan tanggung jawab spesifik. - Tambahkan satu dependency kecil hanya untuk eksperimen, periksa
go mod graph, lalu hapus import dan jalankango mod tidy.
Ringkasan
- Package mengelompokkan kode berdasarkan tanggung jawab; module memberi identitas dan versi pada kumpulan package.
- Struktur proyek harus tumbuh mengikuti kebutuhan, bukan template yang berlebihan.
internalmenyediakan batas akses yang ditegakkan toolchain.go mod tidy,verify, dan pengujian menjaga dependensi tetap konsisten dan dapat direproduksi.