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.

golang + postgresql

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 atau Preload 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

Paket ORM dan library dalam GO

Tautan Lain yang Berguna