Merapikan Struktur: Interface Repository
Memisahkan handler dari penyimpanan data dengan interface agar mudah diganti (database, data palsu untuk tes).
Masalah: handler terikat pada Store
Saat ini handler langsung memakai *Store (penyimpanan di memori). Kelak kamu ingin berpindah ke database PostgreSQL, atau memakai data palsu saat menguji. Jika handler terikat pada *Store, semua handler harus diubah.
Solusi: interface
Interface di Go adalah daftar method yang harus dimiliki sebuah tipe. Tipe apa pun yang punya method-method itu otomatis dianggap memenuhi interface — tanpa kata kunci implements.
type TodoRepository interface {
Add(title string) Todo
Get(id int) (Todo, bool)
}
Handler cukup tahu "ada sesuatu yang bisa Add dan Get". Dari mana datanya, ia tidak peduli:
type Handler struct {
repo TodoRepository // bukan *Store lagi
}
func NewHandler(repo TodoRepository) *Handler {
return &Handler{repo: repo}
}
Satu handler, banyak sumber data
NewHandler(NewMemoryRepo()) // data di memori (produksi sederhana)
NewHandler(NewPostgresRepo()) // nanti: database sungguhan
NewHandler(fakeRepo{}) // saat menguji: data palsu
Struktur folder proyek sungguhan
Seiring besarnya proyek, kode dipisah per tanggung jawab:
todo-api/
├── go.mod
├── cmd/api/main.go ← titik masuk: merangkai semuanya
└── internal/
├── model/todo.go ← struct Todo
├── repository/memory.go ← penyimpanan data
├── handler/todo.go ← handler HTTP
└── middleware/logging.go ← middleware
Folder internal/ membuat paket di dalamnya hanya bisa diimpor oleh proyek ini. Di latihan, semuanya digabung dalam satu file agar mudah dijalankan, tetapi konsepnya identik.