Mulai dengan satu file
Program Go bisa sepenuhnya berada di satu main.go, dan tool kecil memang sebaiknya begitu. Program lengkap ini mem-parsing input, mengerjakan tugasnya, dan mencetak laporan, dalam satu package:
Saat program bertambah besar, pecah menjadi beberapa file di direktori yang sama dan package yang sama (parse.go, report.go, main.go). File-file dalam satu package berbagi setiap identifier, jadi tidak ada yang perlu di-export atau diimpor. Sering kali hanya struktur itulah yang dibutuhkan proyek berukuran beberapa ribu baris.
Ingat bahwa package main yang terdiri dari banyak file harus dijalankan sebagai package: go run ., bukan go run main.go. Kalau tidak, file-file itu dikompilasi sendiri-sendiri dan kamu mendapat error undefined untuk nama yang didefinisikan di file lain.
Tidak ada tata letak yang diwajibkan
Go tidak mewajibkan struktur direktori apa pun selain "satu package per direktori". Panduan resminya adalah halaman Organizing a Go module di go.dev, yang menjelaskan beberapa bentuk, bukan aturan.
Repository GitHub golang-standards/project-layout banyak disalin dan sering dikira sebagai standar. Repository itu adalah kumpulan konvensi komunitas dari proyek-proyek besar, dan Russ Cox, yang saat itu menjadi tech lead Go, membuka issue di sana yang menyatakan bahwa itu bukan standar Go. Kebanyakan direktorinya (pkg/, api/, build/, deployments/) hanya masuk akal untuk codebase yang besar. Menyalin seluruh kerangkanya ke proyek baru hanya menghasilkan folder kosong dan import path yang dalam tanpa manfaat apa pun.
Aturan yang benar-benar ditegakkan oleh tool-nya cukup singkat:
- Satu direktori adalah satu package. Semua file
.godi dalamnya (kecuali file_test.goyang memakai akhiran_test) berbagi nama package yang sama. - Import path adalah path module ditambah path direktori.
- Direktori bernama
internalmembatasi siapa yang boleh mengimpor isinya. - Direktori bernama
testdata, dan yang diawali.atau_, diabaikan oleh tool go.
Bentuk-bentuk yang umum
Satu command
todo/
├── go.mod module github.com/you/todo
├── main.go
├── parse.go
├── report.go
└── parse_test.go
Datar, semuanya di package main. go install github.com/you/todo@latest langsung bisa dipakai dan binary-nya bernama todo.
Sebuah library
slug/
├── go.mod module github.com/you/slug
├── slug.go package slug
├── slug_test.go
├── example_test.go
└── internal/
└── table/ helpers the public package uses, hidden from users
Package-nya berada di root module, jadi pengguna mengimpor github.com/you/slug dan memanggil slug.Make(...). Apa pun yang tidak ingin kamu dukung sebagai API publik masuk ke internal/.
Sebuah service atau beberapa binary
shop/
├── go.mod module github.com/you/shop
├── cmd/
│ ├── shop-api/
│ │ └── main.go package main: flags, config, wiring
│ └── shop-worker/
│ └── main.go
├── internal/
│ ├── order/ package order: domain types and logic
│ │ ├── order.go
│ │ └── order_test.go
│ ├── store/ package store: database access
│ └── httpapi/ package httpapi: handlers, routing
├── migrations/
└── README.md
cmd/<name>/main.go berisi satu direktori per binary, dan nama direktorinya menjadi nama binary (go build ./cmd/shop-api menghasilkan shop-api). Setiap main dibuat tipis: membaca konfigurasi, menyusun dependensi, menjalankan server. Kode yang sebenarnya berada di package-package di bawah internal/, tempat kedua binary bisa memakainya dan module lain tidak bisa.
Inilah tata letak yang akhirnya dipakai kebanyakan service Go. Pakai saat kamu punya binary kedua atau alasan nyata untuk memecah package, bukan sejak hari pertama.
internal/ ditegakkan oleh perintah go
Package di bawah internal/ hanya bisa diimpor oleh kode di pohon direktori yang berakar di induk internal. Dari module lain, build-nya gagal:
package example.com/b
main.go:6:2: use of internal package example.com/a/internal/secret not allowed
Karena itu internal/ adalah alat untuk menjaga permukaan API-mu tetap kecil. Kode di sana bebas diubah, karena kamu tahu setiap pemanggilnya ada di repository-mu sendiri. Untuk sebuah aplikasi, menaruh hampir semuanya di internal/ itu wajar. Untuk sebuah library, direktori ini memisahkan apa yang kamu janjikan tetap stabil dari apa yang tidak.
internal bekerja di kedalaman berapa pun: shop/internal/order terlihat oleh seluruh shop/, sedangkan shop/internal/order/internal/pricing hanya terlihat di dalam shop/internal/order/.
Memberi nama package
Nama package menjadi bagian dari setiap tempat pemanggilan, jadi nama itu lebih penting daripada pohon direktorinya.
- Pendek, huruf kecil, satu kata:
order,store,httpapi. Tanpa garis bawah dan tanpa mixedCaps. - Namai sesuai apa yang disediakannya, bukan apa yang ada di dalamnya.
util,common,helpers,misc, danmodelstidak mengatakan apa-apa dan berubah menjadi tempat pembuangan; panduan gaya dari tim Go secara eksplisit tidak menyarankannya. Taruh helper di package yang memakainya, atau di package yang dinamai sesuai tujuannya (slug,retry). - Hindari pengulangan nama (stutter). Pemanggil menulis
order.Orderdanorder.New, jadi jangan beri nama sepertiorder.OrderServiceatauorder.NewOrder. Standard library melakukannya secara konsisten:http.Server, bukanhttp.HTTPServer. - Nama direktori dan nama package sebaiknya sama, kecuali
package maindi direktori command. Kalau berbeda, pembaca harus membuka file dulu untuk tahu identifier apa yang dihadirkan oleh import itu.
Pecah package berdasarkan tanggung jawab, bukan berdasarkan jenisnya. Pembagian models/, controllers/, services/ (umum di ekosistem lain) memaksa setiap fitur menyentuh tiga package dan cenderung menciptakan import cycle, yang dilarang Go. Package yang disusun di sekitar konsep domain (order, payment, user) masing-masing menyimpan tipe dan logikanya bersama-sama.
Tempat menyimpan test
Test berada di sebelah kodenya di direktori yang sama, dalam file bernama *_test.go. Tidak ada pohon direktori tests/.
package orderdiorder_test.go: test internal, yang bisa memakai identifier yang tidak di-export.package order_testdi direktori yang sama: test eksternal, yang hanya melihat API yang di-export, seperti yang dilihat pemanggil. Berguna untuk contoh dan untuk menghindari import cycle di test.- File fixture disimpan di direktori
testdata/di sebelah test-nya. Tool go mengabaikannya, dan test berjalan dengan direktori package sebagai working directory, jadi path relatif sepertitestdata/big.jsonbisa dipakai.
File lain di root
| File atau direktori | Kegunaan |
|---|---|
go.mod, go.sum | definisi module dan checksum dependensi, selalu di root |
README.md, LICENSE | sama seperti di proyek mana pun |
Makefile atau Taskfile.yml | jalan pintas build yang opsional |
Dockerfile | biasanya di root untuk service |
migrations/, web/, docs/ | aset non-Go, dinamai sesuai isinya |
tools.go | cara lama untuk mengunci dependensi tool; Go 1.24 menggantinya dengan baris tool di go.mod (go get -tool) |
go.work | workspace untuk mengembangkan beberapa module bersamaan secara lokal; biasanya tidak di-commit untuk proyek dengan satu module |
Kesalahan umum
- Menyalin template besar untuk proyek kecil. Mulai dengan struktur datar dan tambahkan direktori saat binary kedua atau batas yang nyata muncul.
- Package bernama
utilsataucommon. Namai package sesuai apa yang dikerjakannya. - Satu package per file atau per tipe. Package Go dimaksudkan sebagai unit yang lebih besar daripada class di Java. Package dengan sepuluh file itu normal.
- Import cycle. Dua package tidak bisa saling mengimpor. Biasanya ini berarti keduanya memang satu kesatuan, bahwa sebuah tipe bersama sebaiknya dipindah ke package yang levelnya lebih rendah, atau bahwa salah satu sisi sebaiknya bergantung pada interface kecil alih-alih pada package lainnya.
pkg/karena kebiasaan. Direktori ini menambah satu segmen ke setiap import path tanpa menambah makna.- Test di direktori terpisah. Di sana test tidak bisa melihat kode yang tidak di-export, dan tooling-nya tidak mengharapkannya.
Pertanyaan yang sering diajukan
Apakah ada tata letak proyek Go yang resmi?
Tidak ada yang wajib. Tim Go menerbitkan panduan di go.dev/doc/modules/layout, yang menjelaskan beberapa bentuk umum (satu package, satu command, beberapa command dengan internal/). Repository golang-standards/project-layout yang populer adalah proyek komunitas, bukan standar Go, dan tim Go sudah menyatakannya secara terbuka.
Apa itu direktori internal di Go?
Package yang import path-nya mengandung elemen internal hanya bisa diimpor oleh kode yang berakar di induk direktori internal tersebut. example.com/app/internal/store bisa diimpor dari mana pun di bawah example.com/app/, dan perintah go menolak import dari module lain dengan use of internal package ... not allowed.
Apakah saya perlu direktori pkg di Go?
Opsional, dan hanya menambah satu segmen path tanpa menambah makna. Beberapa proyek besar memakai pkg/ untuk memisahkan kode library publik dari sisanya, tapi standard library Go dan kebanyakan proyek modern tidak melakukannya. Taruh package yang bisa diimpor di root module atau di direktori yang diberi nama, dan apa pun yang privat di internal/.
Di mana file test disimpan di proyek Go?
Di direktori yang sama dengan kode yang diujinya, dengan nama *_test.go. Go tidak punya pohon direktori test yang terpisah. File fixture disimpan di direktori testdata di sebelah test-nya, yang diabaikan oleh tool go saat mencari package.