← Semua pembelajaran / Go Nol → Enterprise
Fase 4 · Database & Persistensi

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.

Sumber asli gorm.io Resmi Rangkuman ~7 menit baca

Intisari

  • GORM mempercepat CRUD, panel admin, dan prototipe secara nyata.
  • Ia memakai refleksi, jadi kesalahan nama field baru ketahuan saat runtime — kebalikan dari sqlc.
  • Preload menghasilkan 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 menambahkan WHERE 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 / pgxsqlcGORM
Kecepatan menulis CRUDLambatSedangCepat
Kesalahan ketahuan saatRuntimeKompilasiRuntime
SQL terlihatYaYaTidak
Kueri kompleks / laporanBebasBebasTurun ke SQL mentah
Biaya runtimeNolNolRefleksi
Kurva belajarSQLSQLAPI GORM
MigrasiBerkas SQLBerkas SQLAutoMigrate (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

  1. Aplikasi baru yang sudah pasti Postgres: sqlc + pgx. SQL tetap terlihat, tipenya dijaga compiler.
  2. Panel admin internal, alat CRUD: GORM. Kecepatan menulis lebih berharga di sana daripada kendali kueri.
  3. Campuran: sah dan umum. GORM untuk yang membosankan, SQL tangan untuk sepuluh kueri terpanas.
  4. 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.