Comparaison des ORMs Go pour PostgreSQL : GORM vs Ent vs Bun vs sqlc
Un aperçu pratique et axé sur le code des ORMs en GO ```
Les ORM les plus réputés pour GO sont GORM, Ent, Bun et sqlc. Voici une petite comparaison d’entre eux avec des exemples d’opérations CRUD en pure GO.
TL;DR
- GORM : fonctionnalités nombreuses et pratiques ; le plus facile à « simplement déployer », mais avec un surcoût d’exécution plus important.
- Ent : schéma en code avec des API générées et typées ; idéal pour les grands projets et les refactorings.
- Bun : léger, générateur de requêtes SQL-first/ORM ; rapide avec de bonnes fonctionnalités pour Postgres, explicite par conception.
- sqlc (non un ORM au sens strict mais tout de même) : écrivez du SQL, obtenez des fonctions Go typées ; meilleure performance brute et contrôle, pas de magie d’exécution.
Critères de sélection et comparaison rapide
Mes critères sont :
- Performance : latence/throughput, surcoût évitable, opérations par lots.
- DX : courbe d’apprentissage, sécurité des types, débogabilité, friction de la génération de code.
- Écosystème : documentation, exemples, activité, intégrations (migrations, traçage).
- Ensemble de fonctionnalités : relations, chargement anticipé, migrations, hooks, sorties SQL brutes.
Outil | Paradigme | Sécurité des types | Relations | Migrations | Ergonomie du SQL brut | Cas d’utilisation typique |
---|---|---|---|---|---|---|
GORM | ORM de type Active Record | Moyenne (runtime) | Oui (tags, Preload/Joins) | Auto-migrate (optionnel) | db.Raw(...) |
Livraison rapide, fonctionnalités riches, applications CRUD conventionnelles |
Ent | Schéma → génération de code → API fluide | Élevée (compile-time) | Première classe (edges) | SQL généré (étape séparée) | entsql , SQL personnalisé |
Grandes bases de code, équipes en phase de refactor, typage strict |
Bun | Générateur de requêtes SQL-first/ORM | Moyenne–Élevée | Explicite (Relation ) |
Package séparé pour les migrations | Naturel (constructeur + brut) | Services sensibles à la performance, fonctionnalités Postgres |
sqlc | SQL → génération de fonctions (non un ORM) | Élevée (compile-time) | Via les jointures SQL | Outil externe (ex. golang-migrate) | C’est le SQL | Contrôle et vitesse maximum ; équipes amicales aux DBA |
CRUD par exemple
Configuration (PostgreSQL)
Utilisez pgx ou le pilote natif PG de l’outil. Exemple de DSN :
export DATABASE_URL='postgres://user:pass@localhost:5432/app?sslmode=disable'
Importations (communes à tous les ORMs)
Au début de chaque fichier avec go code exemple ajoutez :
import (
"context"
"os"
)
Nous modéliserons une table simple users
:
CREATE TABLE IF NOT EXISTS users (
id BIGSERIAL PRIMARY KEY,
name TEXT NOT NULL,
email TEXT NOT NULL UNIQUE
);
GORM
Initialisation
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 (optionnel ; faites attention en production)
func migrate(db *gorm.DB) error { return db.AutoMigrate(&User{}) }
CRUD
func gormCRUD(ctx context.Context, db *gorm.DB) error {
// Créer
u := User{Name: "Alice", Email: "alice@example.com"}
if err := db.WithContext(ctx).Create(&u).Error; err != nil { return err }
// Lire
var got User
if err := db.WithContext(ctx).First(&got, u.ID).Error; err != nil { return err }
// Mettre à jour
if err := db.WithContext(ctx).Model(&got).
Update("email", "alice+1@example.com").Error; err != nil { return err }
// Supprimer
if err := db.WithContext(ctx).Delete(&User{}, got.ID).Error; err != nil { return err }
return nil
}
Notes
- Relations via les balises de structure +
Preload
/Joins
. - Helper de transaction :
db.Transaction(func(tx *gorm.DB) error { ... })
.
Ent
Définition du schéma (dans 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(),
}
}
Générer du code
go run entgo.io/ent/cmd/ent generate ./ent/schema
Initialisation
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 {
// Créer
u, err := client.User.Create().
SetName("Alice").
SetEmail("alice@example.com").
Save(ctx)
if err != nil { return err }
// Lire
got, err := client.User.Get(ctx, u.ID)
if err != nil { return err }
// Mettre à jour
if _, err := client.User.UpdateOneID(got.ID).
SetEmail("alice+1@example.com").
Save(ctx); err != nil { return err }
// Supprimer
if err := client.User.DeleteOneID(got.ID).Exec(ctx); err != nil { return err }
return nil
}
Notes
- Typage fort de bout en bout ; arêtes pour les relations.
- Migrations générées ou utilisez votre outil de migration de choix.
Bun
Initialisation
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 {
// Créer
u := &User{Name: "Alice", Email: "alice@example.com"}
if _, err := db.NewInsert().Model(u).Exec(ctx); err != nil { return err }
// Lire
var got User
if err := db.NewSelect().Model(&got).
Where("id = ?", u.ID).
Scan(ctx); err != nil { return err }
// Mettre à jour
if _, err := db.NewUpdate().Model(&got).
Set("email = ?", "alice+1@example.com").
WherePK().
Exec(ctx); err != nil { return err }
// Supprimer
if _, err := db.NewDelete().Model(&got).WherePK().Exec(ctx); err != nil { return err }
return nil
}
Notes
- Jointures/explicit loading avec
.Relation("...")
. - Package séparé
bun/migrate
pour les migrations.
sqlc
sqlc n’est techniquement pas un ORM. Vous écrivez du SQL ; il génère des méthodes Go typées.
sqlc.yaml
version: "2"
sql:
- engine: postgresql
queries: db/queries
schema: db/migrations
gen:
go:
package: db
out: internal/db
sql_package: "database/sql" # ou "github.com/jackc/pgx/v5"
Requêtes (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;
Générer
sqlc generate
Utilisation
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)
// Créer
u, err := q.CreateUser(ctx, db.CreateUserParams{
Name: "Alice", Email: "alice@example.com",
})
if err != nil { return err }
// Lire
got, err := q.GetUser(ctx, u.ID)
if err != nil { return err }
// Mettre à jour
up, err := q.UpdateUserEmail(ctx, db.UpdateUserEmailParams{
ID: got.ID, Email: "alice+1@example.com",
})
if err != nil { return err }
// Supprimer
if err := q.DeleteUser(ctx, up.ID); err != nil { return err }
return nil
}
Notes
- Apportez vos propres migrations (ex.
golang-migrate
). - Pour les requêtes dynamiques : écrivez plusieurs variantes SQL ou combinez avec un petit générateur.
Notes sur les performances
- GORM : pratique mais ajoute un surcoût d’abstraction via la réflexion. Bon pour les CRUD classiques ; faites attention aux requêtes N+1 (privilégiez
Joins
ou unPreload
sélectif). - Ent : le code généré évite la réflexion ; bon pour les schémas complexes. Souvent plus rapide que les ORMs lourds avec magie d’exécution.
- Bun : mince sur
database/sql
; rapide, explicite, idéal pour les opérations par lots et les grands ensembles de résultats. - sqlc : performance brute du SQL avec une sécurité au moment de la compilation.
Conseils généraux
- Utilisez pgx comme pilote (v5) et context partout.
- Privilégiez le regroupement (
COPY
,INSERT
multi-lignes) pour un throughput élevé. - Profilage SQL :
EXPLAIN ANALYZE
, index, index couvrants, évitez les allers-retours inutiles. - Réutilisez les connexions ; ajustez la taille du pool en fonction de la charge.
Expérience du développeur et écosystème
- GORM : communauté la plus grande, nombreux exemples/plugins ; courbe d’apprentissage plus raide pour les modèles avancés.
- Ent : documentation excellente ; étape de génération de code est le principal changement de mentalité ; très amical pour les refactorings.
- Bun : requêtes lisibles et prévisibles ; communauté plus petite mais active ; excellentes fonctionnalités Postgres.
- sqlc : dépendances minimales en temps d’exécution ; s’intègre bien avec les outils de migration et le CI ; idéal pour les équipes habituées au SQL.
Points forts des fonctionnalités
- Relations & chargement anticipé : tous gèrent les relations ; GORM (balises +
Preload
/Joins
), Ent (arêtes +.With...()
), Bun (Relation(...)
), sqlc (vous écrivez les jointures). - Migrations : GORM (auto-migrate ; faites attention en production), Ent (SQL généré/diff), Bun (
bun/migrate
), sqlc (outils externes). - Hooks/Extensibilité : GORM (callbacks/plugins), Ent (hooks/middleware + template/codegen), Bun (hooks de requête similaires aux middleware, SQL brut facile), sqlc (composez dans votre couche d’application).
- JSON/Arrays (Postgres) : Bun et GORM ont des helpers pratiques ; Ent/sqlc gèrent via des types personnalisés ou SQL.
Quand choisir quoi
- Choisissez GORM si vous souhaitez une commodité maximale, des fonctionnalités riches et une prototypage rapide pour les services CRUD conventionnels.
- Choisissez Ent si vous valorisez la sécurité au moment de la compilation, des schémas explicites et une maintenance à long terme dans des équipes plus importantes.
- Choisissez Bun si vous souhaitez une performance et des requêtes SQL explicites avec les commodités ORM là où cela aide.
- Choisissez sqlc si vous (et votre équipe) préférez le SQL pur avec des liaisons Go typées et aucun surcoût en temps d’exécution.
docker-compose.yml
minimal pour PostgreSQL local
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
Packages et bibliothèques ORM en GO
Liens utiles
- Feuille de triche Golang
- Performance d’AWS Lambda : JavaScript vs Python vs Golang
- Résoudre l’erreur GORM AutoMigrate PostgreSQL
- Réordonner des documents textuels avec Ollama et modèle d’embedding Qwen3 - en Go
- Réordonner des documents textuels avec Ollama et modèle de réordonnancement Qwen3 - en Go