Een vergelijking van Go-ORMs voor PostgreSQL: GORM vs Ent vs Bun vs sqlc
Praktisch, code-intensief overzicht van ORMs in GO ```
Inhoud
De meest prominente ORMs voor GO zijn GORM, Ent, Bun en sqlc. Hieronder volgt een kleine vergelijking van hen met voorbeelden van CRUD-acties in zuivere GO.
TL;DR
- GORM: veel functies en handig; makkelijkst om “direct te implementeren”, maar heeft meer runtime-overhead.
- Ent: schema als code met gegenereerde, typeveilige API’s; uitstekend voor grote codebases en herschikkingen.
- Bun: lichtgewicht, SQL-georiënteerd querybuilder/ORM; snel met uitstekende Postgres-functies, expliciet ontworpen.
- sqlc (niet echt een ORM, maar wel): schrijf SQL, krijg typeveilige Go; beste ruwe prestaties en controle, geen runtime-magie.
Selectiekriteria en snelle vergelijking
Mijn criteria zijn:
- Prestaties: latentie/doorvoer, vermijdbare overhead, batch-acties.
- Gebruikerservaring: leercurve, typeveiligheid, debugbaarheid, codegen-wrijving.
- Ecosysteem: documentatie, voorbeelden, activiteit, integraties (migraties, tracing).
- Functieverzameling: relaties, eager loading, migraties, hooks, escapehatches voor ruwe SQL.
Tool | Paradigma | Typeveiligheid | Relaties | Migraties | Ruwe SQL-ergonomie | Typische gebruikscase |
---|---|---|---|---|---|---|
GORM | Active Record-stijl ORM | Gemiddeld (runtime) | Ja (tags, Preload/Joins) | Automigreren (opt-in) | db.Raw(...) |
Snel implementeren, rijke functies, conventionele CRUD-apps |
Ent | Schema → codegen → fluïde API | Hoog (compile-time) | Eersteklas (edges) | Gegenereerde SQL (afzonderlijk stap) | entsql , aangepaste SQL |
Grote codebases, herschikkingen-gevoelige teams, strikte typen |
Bun | SQL-georiënteerd querybuilder/ORM | Gemiddeld–Hoog | Expliciet (Relation ) |
Afzonderlijk migratiepakket | Natuurlijk (builder + ruw) | Prestatiebewuste diensten, Postgres-functies |
sqlc | SQL → codegen functies (niet een ORM) | Hoog (compile-time) | Via SQL joins | Externe tool (bijv. golang-migrate) | Het is SQL | Maximaal controle & snelheid; DBA-vriendelijke teams |
CRUD via voorbeeld
Instellen (PostgreSQL)
Gebruik pgx of het native PG-drijver van het hulpprogramma. Voorbeeld DSN:
export DATABASE_URL='postgres://user:pass@localhost:5432/app?sslmode=disable'
Imports (algemeen voor alle ORMs)
Aan het begin van elk bestand met go code voorbeeld voeg toe:
import (
"context"
"os"
)
We zullen een eenvoudige users
-tabel modelleren:
CREATE TABLE IF NOT EXISTS users (
id BIGSERIAL PRIMARY KEY,
name TEXT NOT NULL,
email TEXT NOT NULL UNIQUE
);
GORM
Init
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{})
}
// Automigreren (optioneel; wees voorzichtig in productie)
func migrate(db *gorm.DB) error { return db.AutoMigrate(&User{}) }
CRUD
func gormCRUD(ctx context.Context, db *gorm.DB) error {
// Aanmaken
u := User{Name: "Alice", Email: "alice@example.com"}
if err := db.WithContext(ctx).Create(&u).Error; err != nil { return err }
// Lezen
var got User
if err := db.WithContext(ctx).First(&got, u.ID).Error; err != nil { return err }
// Bijwerken
if err := db.WithContext(ctx).Model(&got).
Update("email", "alice+1@example.com").Error; err != nil { return err }
// Verwijderen
if err := db.WithContext(ctx).Delete(&User{}, got.ID).Error; err != nil { return err }
return nil
}
Opmerkingen
- Relaties via struct-tags +
Preload
/Joins
. - Transactiehelper:
db.Transaction(func(tx *gorm.DB) error { ... })
.
Ent
Schema definitie (in 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(),
}
}
Genereer code
go run entgo.io/ent/cmd/ent generate ./ent/schema
Init
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 {
// Aanmaken
u, err := client.User.Create().
SetName("Alice").
SetEmail("alice@example.com").
Save(ctx)
if err != nil { return err }
// Lezen
got, err := client.User.Get(ctx, u.ID)
if err != nil { return err }
// Bijwerken
if _, err := client.User.UpdateOneID(got.ID).
SetEmail("alice+1@example.com").
Save(ctx); err != nil { return err }
// Verwijderen
if err := client.User.DeleteOneID(got.ID).Exec(ctx); err != nil { return err }
return nil
}
Opmerkingen
- Sterke typen van eind tot eind; edges voor relaties.
- Gegenereerde migraties of gebruik uw favoriete migratiehulpmiddel.
Bun
Init
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 {
// Aanmaken
u := &User{Name: "Alice", Email: "alice@example.com"}
if _, err := db.NewInsert().Model(u).Exec(ctx); err != nil { return err }
// Lezen
var got User
if err := db.NewSelect().Model(&got).
Where("id = ?", u.ID).
Scan(ctx); err != nil { return err }
// Bijwerken
if _, err := db.NewUpdate().Model(&got).
Set("email = ?", "alice+1@example.com").
WherePK().
Exec(ctx); err != nil { return err }
// Verwijderen
if _, err := db.NewDelete().Model(&got).WherePK().Exec(ctx); err != nil { return err }
return nil
}
Opmerkingen
- Expliciete joins/eager loading met
.Relation("...")
. - Afzonderlijk
bun/migrate
-pakket voor migraties.
sqlc
sqlc is geen ORM. U schrijft SQL; het genereert typeveilige Go-methoden.
sqlc.yaml
version: "2"
sql:
- engine: postgresql
queries: db/queries
schema: db/migrations
gen:
go:
package: db
out: internal/db
sql_package: "database/sql" # of "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;
Genereer
sqlc generate
Gebruik
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)
// Aanmaken
u, err := q.CreateUser(ctx, db.CreateUserParams{
Name: "Alice", Email: "alice@example.com",
})
if err != nil { return err }
// Lezen
got, err := q.GetUser(ctx, u.ID)
if err != nil { return err }
// Bijwerken
up, err := q.UpdateUserEmail(ctx, db.UpdateUserEmailParams{
ID: got.ID, Email: "alice+1@example.com",
})
if err != nil { return err }
// Verwijderen
if err := q.DeleteUser(ctx, up.ID); err != nil { return err }
return nil
}
Opmerkingen
- Breng uw eigen migraties mee (bijv.
golang-migrate
). - Voor dynamische queries: schrijf meerdere SQL-varianten of combineer met een klein builder.
Prestatieopmerkingen
- GORM: handig maar voegt reflectie/abstractie overhead toe. Goed voor typische CRUD; let op N+1 queries (voorkeur voor
Joins
of selectievePreload
). - Ent: gegenereerde code vermijdt reflectie; goed voor complexe schema’s. Vaak sneller dan zware, runtime-magie ORMs.
- Bun: dun over
database/sql
; snel, expliciet, uitstekend voor batch-acties en grote resultatenverzamelingen. - sqlc: essentieel ruwe SQL-prestaties met compile-time veiligheid.
Algemene tips
- Gebruik pgx als drijver (v5) en context overal.
- Voorkeur geven aan batchverwerking (
COPY
, meervoudige rijINSERT
) voor hoge doorvoer. - Profielen SQL:
EXPLAIN ANALYZE
, indexen, covering indexen, vermijd onnodige rondreizen. - Verwerk verbindingen; stel poolgrootte in op basis van werkbelasting.
Ontwikkelaarservaring en ecosysteem
- GORM: grootste gemeenschap, veel voorbeelden/plugins; steile leercurve voor geavanceerde patronen.
- Ent: uitstekende documentatie; codegen-stap is de hoofd mentale modelshift; super herschikking-vriendelijk.
- Bun: leesbare, voorspelbare queries; kleinere maar actieve gemeenschap; uitstekende Postgres-nicaties.
- sqlc: minimale runtime-afhankelijkheden; integreert goed met migratiehulpmiddelen en CI; uitstekend voor teams die comfortabel zijn met SQL.
Functieoverzicht
- Relaties & eager loading: allemaal relaties verwerken; GORM (tags +
Preload
/Joins
), Ent (edges +.With...()
), Bun (Relation(...)
), sqlc (u schrijft de joins). - Migraties: GORM (automigreren; wees voorzichtig in productie), Ent (gegenereerde/diff SQL), Bun (
bun/migrate
), sqlc (externe tools). - Hooks/Extensibiliteit: GORM (callbacks/plugins), Ent (hooks/middleware + template/codegen), Bun (middleware-achtige queryhooks, eenvoudige ruwe SQL), sqlc (componeren in uw app-laag).
- JSON/Arrays (Postgres): Bun en GORM hebben handige helpers; Ent/sqlc verwerken via aangepaste typen of SQL.
Wanneer wat kiezen
- Kies GORM als u maximale handigheid, rijke functies en snelle prototyping voor conventionele CRUD-diensten wilt.
- Kies Ent als u compile-time veiligheid, expliciete schema’s en langdurige onderhoudbaarheid in grotere teams waardeert.
- Kies Bun als u prestaties en expliciete SQL-gevormde queries met ORM-comforten waar het helpt wilt.
- Kies sqlc als u (en uw team) voorkeur geeft aan zuivere SQL met typeveilige Go-bindingen en nul runtime-overhead.
Minimal docker-compose.yml
voor lokale PostgreSQL
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