GORM — dan kapan ORM masuk akal
GORM membuat CRUD jadi beberapa baris dan relasi jadi otomatis. Harganya adalah SQL yang tidak lagi kamu lihat — dan di aplikasi bertrafik tinggi, SQL yang tidak terlihat adalah SQL yang tidak dioptimalkan.
Intisari
- GORM mempercepat CRUD, panel admin, dan prototipe secara nyata.
- Ia memakai refleksi, jadi kesalahan nama field baru ketahuan saat runtime — kebalikan dari sqlc.
Preloadmenghasilkan kueri terpisah per relasi; tanpa itu, kamu dapat N+1 yang tidak terlihat.- Soft delete aktif secara default begitu ada
gorm.DeletedAt— kueri diam-diam menambahkanWHERE deleted_at IS NULL. - Sah dipakai berdampingan: GORM untuk admin, sqlc/SQL untuk jalur panas.
Seperti apa rasanya
import (
"gorm.io/driver/postgres"
"gorm.io/gorm"
)
type Produk struct {
ID uint `gorm:"primarykey"`
Nama string `gorm:"size:200;not null;index"`
Harga int64 `gorm:"not null"`
KategoriID uint
Kategori Kategori
CreatedAt time.Time
UpdatedAt time.Time
DeletedAt gorm.DeletedAt `gorm:"index"` // ← mengaktifkan soft delete
}
db, err := gorm.Open(postgres.Open(dsn), &gorm.Config{})
db.WithContext(ctx).Create(&p)
db.WithContext(ctx).First(&p, id)
db.WithContext(ctx).Where("harga < ?", 50000).Find(&daftar)
db.WithContext(ctx).Model(&p).Update("harga", 42000)
db.WithContext(ctx).Delete(&p) // soft delete
db.WithContext(ctx).Preload("Kategori").Find(&daftar)
WithContext(ctx) gampang lupa, dan itu menghapus seluruh rantai pembatalan.
Tanpa itu, kueri tetap berjalan setelah pengguna menutup koneksi dan setelah tenggat permintaan lewat.
Jadikan aturan review: setiap panggilan GORM diawali WithContext.
Tiga jebakan performa
1. N+1 yang tersembunyi
// 1 kueri untuk produk, lalu 1 kueri PER produk untuk kategorinya
var daftar []Produk
db.Find(&daftar)
for _, p := range daftar {
fmt.Println(p.Kategori.Nama) // relasi kosong / lazy load
}
// ✅ 2 kueri, berapa pun jumlah barisnya
db.Preload("Kategori").Find(&daftar)
// ✅ 1 kueri kalau memang cuma butuh beberapa kolom
db.Joins("Kategori").Select("produk.id, produk.nama, Kategori__nama").Find(&daftar)
2. SELECT * di mana-mana
// Mengambil SEMUA kolom, termasuk teks panjang yang tidak dipakai
db.Find(&daftar)
// Ambil yang perlu saja
db.Select("id", "nama", "harga").Find(&daftar)
3. Soft delete yang diam-diam ikut
// Ini SEBENARNYA menjalankan:
// SELECT * FROM produk WHERE id = 7 AND deleted_at IS NULL
db.First(&p, 7)
// Untuk benar-benar melihat semuanya:
db.Unscoped().First(&p, 7)
db.Unscoped().Delete(&p) // hapus permanen
Soft delete adalah fitur yang paling sering mengejutkan. Ia aktif hanya karena ada field
gorm.DeletedAt — tanpa kamu menyalakan apa pun. Akibatnya: unique constraint jadi
bermasalah (baris "terhapus" masih memakai nilai unik), count tidak cocok dengan
psql, dan index perlu memperhitungkan deleted_at.
Melihat SQL yang sebenarnya jalan
db, _ := gorm.Open(postgres.Open(dsn), &gorm.Config{
Logger: logger.Default.LogMode(logger.Info), // cetak semua kueri
})
// Atau: hanya kueri yang lebih lambat dari ambang
gormLog := logger.New(log.New(os.Stdout, "", 0), logger.Config{
SlowThreshold: 200 * time.Millisecond,
LogLevel: logger.Warn,
})
Nyalakan log kueri saat mengembangkan, dan lihat sungguhan. Sebagian besar kejutan performa GORM hilang begitu kamu terbiasa membaca SQL yang dihasilkannya. Yang berbahaya bukan ORM-nya — melainkan memakai ORM tanpa pernah melihat keluarannya.
Perbandingan jujur
| SQL manual / pgx | sqlc | GORM | |
|---|---|---|---|
| Kecepatan menulis CRUD | Lambat | Sedang | Cepat |
| Kesalahan ketahuan saat | Runtime | Kompilasi | Runtime |
| SQL terlihat | Ya | Ya | Tidak |
| Kueri kompleks / laporan | Bebas | Bebas | Turun ke SQL mentah |
| Biaya runtime | Nol | Nol | Refleksi |
| Kurva belajar | SQL | SQL | API GORM |
| Migrasi | Berkas SQL | Berkas SQL | AutoMigrate (jangan di produksi) |
AutoMigrate tidak layak untuk produksi. Ia tidak menghapus kolom, tidak
mengubah tipe dengan aman, tidak bisa di-review sebagai diff, dan tidak punya jalur mundur. Pakai berkas
migrasi (materi sebelumnya) bahkan kalau kamu memakai GORM untuk sisanya.
Rekomendasi
- Aplikasi baru yang sudah pasti Postgres: sqlc + pgx. SQL tetap terlihat, tipenya dijaga compiler.
- Panel admin internal, alat CRUD: GORM. Kecepatan menulis lebih berharga di sana daripada kendali kueri.
- Campuran: sah dan umum. GORM untuk yang membosankan, SQL tangan untuk sepuluh kueri terpanas.
- Apa pun pilihannya: berkas migrasi terpisah, dan lihat SQL yang benar-benar jalan.
Latihan: tulis endpoint daftar produk beserta kategorinya dengan GORM tanpa
Preload, nyalakan log kueri, dan hitung berapa kueri yang muncul untuk 20 produk. Tambahkan
Preload dan hitung lagi. Terakhir, tulis kueri yang sama dengan sqlc dan bandingkan jumlah
perjalanan ke database.
Rangkuman ini sengaja dipangkas ke bagian yang dipakai di roadmap. Buka sumber aslinya saat kamu butuh detail lengkap atau referensi parameter.