Membandingkan ORM Go untuk PostgreSQL: GORM vs Ent vs Bun vs sqlc
Pandangan praktis yang penuh kode tentang ORM di GO
Konten Halaman
ORM paling menonjol untuk GO adalah GORM, Ent, Bun, dan sqlc. Berikut adalah perbandingan kecil antara mereka dengan contoh operasi CRUD dalam GO murni.
TL;DR
- GORM: fitur lengkap dan nyaman; paling mudah untuk “langsung mengirim”, tetapi memiliki lebih banyak overhead runtime.
- Ent: schema sebagai kode dengan API yang dihasilkan dan aman tipe; sangat cocok untuk kodebase besar dan refactor.
- Bun: ringan, pembangun query/ORM berbasis SQL; cepat dengan fitur Postgres yang hebat, dirancang eksplisit.
- sqlc (bukan ORM sebenarnya tetapi tetap): tulis SQL, dapatkan Go yang aman tipe; kinerja mentah terbaik dan kontrol, tanpa magis runtime.
Kriteria Pemilihan dan Perbandingan Cepat
Kriteria saya adalah:
- Kinerja: latensi/throughput, overhead yang dapat dihindari, operasi batch.
- DX: kurva pembelajaran, keamanan tipe, kemudahan debugging, gesekan codegen.
- Ekosistem: dokumentasi, contoh, aktivitas, integrasi (migrasi, pelacakan).
- Setelan fitur: relasi, loading cepat, migrasi, hook, escape hatches SQL mentah.
Alat | Paradigma | Keamanan Tipe | Relasi | Migrasi | Keterbacaan SQL Mentah | Kasus Penggunaan Tipe |
---|---|---|---|---|---|---|
GORM | ORM Gaya Active Record | Sedang (runtime) | Ya (tag, Preload/Joins) | Auto-migrate (opt-in) | db.Raw(...) |
Pengiriman cepat, fitur kaya, aplikasi CRUD konvensional |
Ent | Schema → codegen → API yang lancar | Tinggi (compile-time) | Kelas satu (edges) | SQL yang dihasilkan (langkah terpisah) | entsql , SQL kustom |
Kodebase besar, tim refactor-heavy, tipe ketat |
Bun | Pembangun query/ORM berbasis SQL | Sedang–Tinggi | Eksplisit (Relation ) |
Paket migrasi terpisah | Alami (builder + mentah) | Layanan berbasis kinerja, fitur Postgres |
sqlc | SQL → fungsi codegen (bukan ORM) | Tinggi (compile-time) | Melalui join SQL | Alat eksternal (misalnya, golang-migrate) | It is SQL | Kontrol & kecepatan maksimal; tim ramah DBA |
CRUD Berdasarkan Contoh
Pengaturan (PostgreSQL)
Gunakan pgx atau driver PG bawaan alat. Contoh DSN:
export DATABASE_URL='postgres://user:pass@localhost:5432/app?sslmode=disable'
Impor (umum untuk semua ORMs)
Di awal setiap file dengan go code contoh tambahkan:
import (
"context"
"os"
)
Kita akan memodelkan tabel sederhana users
:
CREATE TABLE IF NOT EXISTS users (
id BIGSERIAL PRIMARY KEY,
name TEXT NOT NULL,
email TEXT NOT NULL UNIQUE
);
GORM
Inisialisasi
import (
"gorm.io/driver/postgres"
"gorm.io/gorm"
)
type User struct {
ID int64 `gorm:"primaryKey"`
Name string
Email string `gorm:"uniqueIndex"`
}
func newGorm() (*gorm.DB, error) {
dsn := os.Getenv("DATABASE_URL")
return gorm.Open(postgres.Open(dsn), &gorm.Config{})
}
// Auto-migrate (opsional; hati-hati di prod)
func migrate(db *gorm.DB) error { return db.AutoMigrate(&User{}) }
CRUD
func gormCRUD(ctx context.Context, db *gorm.DB) error {
// Membuat
u := User{Name: "Alice", Email: "alice@example.com"}
if err := db.WithContext(ctx).Create(&u).Error; err != nil { return err }
// Membaca
var got User
if err := db.WithContext(ctx).First(&got, u.ID).Error; err != nil { return err }
// Memperbarui
if err := db.WithContext(ctx).Model(&got).
Update("email", "alice+1@example.com").Error; err != nil { return err }
// Menghapus
if err := db.WithContext(ctx).Delete(&User{}, got.ID).Error; err != nil { return err }
return nil
}
Catatan
- Relasi melalui tag struct +
Preload
/Joins
. - Bantuan transaksi:
db.Transaction(func(tx *gorm.DB) error { ... })
.
Ent
Definisi schema (dalam ent/schema/user.go
):
package schema
import (
"entgo.io/ent"
"entgo.io/ent/schema/field"
)
type User struct {
ent.Schema
}
func (User) Fields() []ent.Field {
return []ent.Field{
field.Int64("id").Unique().Immutable(),
field.String("name"),
field.String("email").Unique(),
}
}
Generate code
go run entgo.io/ent/cmd/ent generate ./ent/schema
Inisialisasi
import (
"entgo.io/ent/dialect"
"entgo.io/ent/dialect/sql"
_ "github.com/jackc/pgx/v5/stdlib"
"your/module/ent"
)
func newEnt() (*ent.Client, error) {
dsn := os.Getenv("DATABASE_URL")
drv, err := sql.Open(dialect.Postgres, dsn)
if err != nil { return nil, err }
return ent.NewClient(ent.Driver(drv)), nil
}
CRUD
func entCRUD(ctx context.Context, client *ent.Client) error {
// Membuat
u, err := client.User.Create().
SetName("Alice").
SetEmail("alice@example.com").
Save(ctx)
if err != nil { return err }
// Membaca
got, err := client.User.Get(ctx, u.ID)
if err != nil { return err }
// Memperbarui
if _, err := client.User.UpdateOneID(got.ID).
SetEmail("alice+1@example.com").
Save(ctx); err != nil { return err }
// Menghapus
if err := client.User.DeleteOneID(got.ID).Exec(ctx); err != nil { return err }
return nil
}
Catatan
- Tipe kuat end-to-end; edges untuk relasi.
- Migrasi yang dihasilkan atau gunakan alat migrasi pilihan Anda.
Bun
Inisialisasi
import (
"database/sql"
"github.com/uptrace/bun"
"github.com/uptrace/bun/dialect/pgdialect"
_ "github.com/jackc/pgx/v5/stdlib"
)
type User struct {
bun.BaseModel `bun:"table:users"`
ID int64 `bun:",pk,autoincrement"`
Name string `bun:",notnull"`
Email string `bun:",unique,notnull"`
}
func newBun() (*bun.DB, error) {
dsn := os.Getenv("DATABASE_URL")
sqldb, err := sql.Open("pgx", dsn)
if err != nil { return nil, err }
return bun.NewDB(sqldb, pgdialect.New()), nil
}
CRUD
func bunCRUD(ctx context.Context, db *bun.DB) error {
// Membuat
u := &User{Name: "Alice", Email: "alice@example.com"}
if _, err := db.NewInsert().Model(u).Exec(ctx); err != nil { return err }
// Membaca
var got User
if err := db.NewSelect().Model(&got).
Where("id = ?", u.ID).
Scan(ctx); err != nil { return err }
// Memperbarui
if _, err := db.NewUpdate().Model(&got).
Set("email = ?", "alice+1@example.com").
WherePK().
Exec(ctx); err != nil { return err }
// Menghapus
if _, err := db.NewDelete().Model(&got).WherePK().Exec(ctx); err != nil { return err }
return nil
}
Catatan
- Join/eager loading eksplisit dengan
.Relation("...")
. - Paket
bun/migrate
terpisah untuk migrasi.
sqlc
sqlc bukan ORM. Anda menulis SQL; ia menghasilkan metode Go yang aman tipe.
sqlc.yaml
version: "2"
sql:
- engine: postgresql
queries: db/queries
schema: db/migrations
gen:
go:
package: db
out: internal/db
sql_package: "database/sql" # atau "github.com/jackc/pgx/v5"
Queries (db/queries/users.sql
)
-- name: CreateUser :one
INSERT INTO users (name, email)
VALUES ($1, $2)
RETURNING id, name, email;
-- name: GetUser :one
SELECT id, name, email FROM users WHERE id = $1;
-- name: UpdateUserEmail :one
UPDATE users SET email = $2 WHERE id = $1
RETURNING id, name, email;
-- name: DeleteUser :exec
DELETE FROM users WHERE id = $1;
Generate
sqlc generate
Penggunaan
import (
"database/sql"
_ "github.com/jackc/pgx/v5/stdlib"
"your/module/internal/db"
)
func sqlcCRUD(ctx context.Context) error {
dsn := os.Getenv("DATABASE_URL")
sqldb, err := sql.Open("pgx", dsn)
if err != nil { return err }
q := db.New(sqldb)
// Membuat
u, err := q.CreateUser(ctx, db.CreateUserParams{
Name: "Alice", Email: "alice@example.com",
})
if err != nil { return err }
// Membaca
got, err := q.GetUser(ctx, u.ID)
if err != nil { return err }
// Memperbarui
up, err := q.UpdateUserEmail(ctx, db.UpdateUserEmailParams{
ID: got.ID, Email: "alice+1@example.com",
})
if err != nil { return err }
// Menghapus
if err := q.DeleteUser(ctx, up.ID); err != nil { return err }
return nil
}
Catatan
- Bawa migrasi Anda sendiri (misalnya,
golang-migrate
). - Untuk query dinamis: tulis beberapa variant SQL atau gabungkan dengan pembangun kecil.
Catatan Kinerja
- GORM: nyaman tetapi menambahkan overhead refleksi/abstraksi. Cocok untuk CRUD biasa; waspadai query N+1 (lebih baik gunakan
Joins
atauPreload
yang selektif). - Ent: kode yang dihasilkan menghindari refleksi; baik untuk skema kompleks. Seringkali lebih cepat dari ORM berbasis magis runtime yang berat.
- Bun: tipis di atas
database/sql
; cepat, eksplisit, hebat untuk operasi batch dan set besar hasil. - sqlc: kinerja SQL mentah dengan keamanan compile-time.
Tips Umum
- Gunakan pgx sebagai driver (v5) dan context di mana-mana.
- Preferensi batching (
COPY
,INSERT
multi-baris) untuk throughput tinggi. - Profil SQL:
EXPLAIN ANALYZE
, indeks, indeks menutupi, hindari perjalanan tak perlu. - Ulangi koneksi; atur ukuran pool berdasarkan beban kerja.
Pengalaman Pengembang dan Ekosistem
- GORM: komunitas terbesar, banyak contoh/plugin; kurva pembelajaran lebih curam untuk pola lanjutan.
- Ent: dokumentasi hebat; langkah codegen adalah perubahan model mental utama; sangat ramah refactor.
- Bun: query yang terbaca dan dapat diprediksi; komunitas lebih kecil tetapi aktif; fitur Postgres yang hebat.
- sqlc: ketergantungan runtime minimal; terintegrasi dengan baik dengan alat migrasi dan CI; superb untuk tim yang nyaman dengan SQL.
Fitur Unggulan
- Relasi & loading cepat: semua menangani relasi; GORM (tag +
Preload
/Joins
), Ent (edges +.With...()
), Bun (Relation(...)
), sqlc (Anda menulis join). - Migrasi: GORM (auto-migrate; hati-hati di prod), Ent (SQL yang dihasilkan/diff), Bun (
bun/migrate
), sqlc (alat eksternal). - Hook/Extensibilitas: GORM (callback/plugin), Ent (hook/middleware + template/codegen), Bun (hook query mirip middleware, SQL mentah mudah), sqlc (komposisi di lapisan aplikasi).
- JSON/Array (Postgres): Bun dan GORM memiliki bantuan yang bagus; Ent/sqlc menangani melalui tipe kustom atau SQL.
Kapan Memilih Apa
- Pilih GORM jika Anda ingin kenyamanan maksimal, fitur kaya, dan prototyping cepat untuk layanan CRUD konvensional.
- Pilih Ent jika Anda menghargai keamanan compile-time, schema eksplisit, dan pemeliharaan jangka panjang di tim besar.
- Pilih Bun jika Anda ingin kinerja dan query SQL berbentuk eksplisit dengan kenyamanan ORM di tempat yang membantu.
- Pilih sqlc jika Anda (dan tim Anda) lebih suka SQL murni dengan ikatan Go yang aman tipe dan tanpa overhead runtime.
docker-compose.yml
Minimal untuk PostgreSQL Lokal
version: "3.8"
services:
db:
image: postgres:16
environment:
POSTGRES_USER: user
POSTGRES_PASSWORD: pass
POSTGRES_DB: app
ports: ["5432:5432"]
healthcheck:
test: ["CMD-SHELL", "pg_isready -U user -d app"]
interval: 5s
timeout: 3s
retries: 5