مقارنة ORMs لـ Go لـ PostgreSQL: GORM مقابل Ent مقابل Bun مقابل sqlc
نظرة عملية ومليئة بالكود على ORMs في GO
Page content
أبرز ORMs لـ GO هي GORM، Ent، Bun و sqlc. هنا مقارنة بسيطة بينها مع أمثلة على عمليات CRUD في GO النقي.
TL;DR
- GORM: ميزات كثيرة ومريحة؛ الأسهل لـ “just ship”، لكنه يحتوي على مزيد من التحميل أثناء التشغيل.
- Ent: نموذج الشيفرة مع واجهات API مُولدة ومُحصنة نوعًا؛ مثالي لقواعد البيانات الكبيرة والتعديلات.
- Bun: خفيف، مُنشئ استعلامات SQL أولاً/ORM؛ سريع مع ميزات Postgres الرائعة، صريح بالتصميم.
- sqlc (ليس ORM بالمعنى الحرفي ولكن لا يزال): اكتب SQL، احصل على Go مُحصنة نوعًا؛ الأداء الأفضل والتحكم الأكبر، بدون سحر أثناء التشغيل.
معايير الاختيار والمقارنة السريعة
معاييري هي:
- الأداء: التأخير/التدفق، التحميل القابل لتجنبه، العمليات الجماعية.
- تجربة المطور (DX): منحنى التعلم، الأمان النوعي، قابلية التصحيح، مقاومة إنشاء الشيفرة.
- البيئة: الوثائق، الأمثلة، النشاط، التكاملات (التحديثات، تتبع الأداء).
- مجموعة الميزات: العلاقات، التحميل المبكر، التحديثات، التصحيحات، مخارج SQL نقي.
الأداة | المنهجية | الأمان النوعي | العلاقات | التحديثات | سهولة استخدام SQL الخام | حالة الاستخدام النموذجية |
---|---|---|---|---|---|---|
GORM | ORM من نوع Active Record | متوسط (أثناء التشغيل) | نعم (العلامات، Preload/Joins) | التحديث التلقائي (اختياري) | db.Raw(...) |
تسليم سريع، ميزات غنية، تطبيقات CRUD التقليدية |
Ent | النموذج → إنشاء الشيفرة → واجهة API سلسة | مرتفع (أثناء التجميع) | أولوية (الحواف) | SQL مُولدة (خطوة منفصلة) | entsql ، SQL مخصص |
قواعد بيانات كبيرة، فرق تعديلات كثيرة، تقييد نوعي صارم |
Bun | مُنشئ استعلامات SQL أولاً/ORM | متوسط–مرتفع | صريح (Relation ) |
حزمة منفصلة للتحديثات | طبيعي (المُنشئ + الخام) | خدمات مهتمة بالأداء، ميزات Postgres |
sqlc | SQL → إنشاء دوال (ليس ORM بالمعنى الحرفي) | مرتفع (أثناء التجميع) | عبر توصيلات SQL | أداة خارجية (مثل golang-migrate) | هو SQL | التحكم والسرعة الأقصى؛ فرق مهتمة بـ DBA |
CRUD عبر أمثلة
الإعداد (PostgreSQL)
استخدم pgx أو محرك PG الخاص بالأداة. مثال DSN:
export DATABASE_URL='postgres://user:pass@localhost:5432/app?sslmode=disable'
المستورات (شائعة لجميع ORMs)
في بداية كل ملف مع كود GO مثال أضف:
import (
"context"
"os"
)
سنتعامل مع جدول بسيط users
:
CREATE TABLE IF NOT EXISTS users (
id BIGSERIAL PRIMARY KEY,
name TEXT NOT NULL,
email TEXT NOT NULL UNIQUE
);
GORM
البدء
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{})
}
// التحديث التلقائي (اختياري؛ كن حذرًا في الإنتاج)
func migrate(db *gorm.DB) error { return db.AutoMigrate(&User{}) }
CRUD
func gormCRUD(ctx context.Context, db *gorm.DB) error {
// إنشاء
u := User{Name: "Alice", Email: "alice@example.com"}
if err := db.WithContext(ctx).Create(&u).Error; err != nil { return err }
// القراءة
var got User
if err := db.WithContext(ctx).First(&got, u.ID).Error; err != nil { return err }
// التحديث
if err := db.WithContext(ctx).Model(&got).
Update("email", "alice+1@example.com").Error; err != nil { return err }
// الحذف
if err := db.WithContext(ctx).Delete(&User{}, got.ID).Error; err != nil { return err }
return nil
}
ملاحظات
- العلاقات عبر العلامات +
Preload
/Joins
. - مساعدة التحويل:
db.Transaction(func(tx *gorm.DB) error { ... })
.
Ent
تعريف النموذج (في 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(),
}
}
إنشاء الشيفرة
go run entgo.io/ent/cmd/ent generate ./ent/schema
البدء
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 {
// إنشاء
u, err := client.User.Create().
SetName("Alice").
SetEmail("alice@example.com").
Save(ctx)
if err != nil { return err }
// القراءة
got, err := client.User.Get(ctx, u.ID)
if err != nil { return err }
// التحديث
if _, err := client.User.UpdateOneID(got.ID).
SetEmail("alice+1@example.com").
Save(ctx); err != nil { return err }
// الحذف
if err := client.User.DeleteOneID(got.ID).Exec(ctx); err != nil { return err }
return nil
}
ملاحظات
- الأمان النوعي من البداية إلى النهاية؛ الحواف للعلاقات.
- تحديثات مُولدة أو استخدم أداة التحديث المفضلة لديك.
Bun
البدء
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 {
// إنشاء
u := &User{Name: "Alice", Email: "alice@example.com"}
if _, err := db.NewInsert().Model(u).Exec(ctx); err != nil { return err }
// القراءة
var got User
if err := db.NewSelect().Model(&got).
Where("id = ?", u.ID).
Scan(ctx); err != nil { return err }
// التحديث
if _, err := db.NewUpdate().Model(&got).
Set("email = ?", "alice+1@example.com").
WherePK().
Exec(ctx); err != nil { return err }
// الحذف
if _, err := db.NewDelete().Model(&got).WherePK().Exec(ctx); err != nil { return err }
return nil
}
ملاحظات
- التوصيلات/التحميل المبكر صريح مع
.Relation("...")
. - حزمة منفصلة
bun/migrate
للتحديثات.
sqlc
sqlc تقنيًا ليس ORM. تكتب SQL؛ تُولد دوال Go مُحصنة نوعًا.
sqlc.yaml
version: "2"
sql:
- engine: postgresql
queries: db/queries
schema: db/migrations
gen:
go:
package: db
out: internal/db
sql_package: "database/sql" # أو "github.com/jackc/pgx/v5"
الاستعلامات (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;
إنشاء
sqlc generate
الاستخدام
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)
// إنشاء
u, err := q.CreateUser(ctx, db.CreateUserParams{
Name: "Alice", Email: "alice@example.com",
})
if err != nil { return err }
// القراءة
got, err := q.GetUser(ctx, u.ID)
if err != nil { return err }
// التحديث
up, err := q.UpdateUserEmail(ctx, db.UpdateUserEmailParams{
ID: got.ID, Email: "alice+1@example.com",
})
if err != nil { return err }
// الحذف
if err := q.DeleteUser(ctx, up.ID); err != nil { return err }
return nil
}
ملاحظات
- استخدم أدوات التحديث الخاصة بك (مثل
golang-migrate
). - للأسئلة الديناميكية: اكتب عدة نسخ من SQL أو ادمج مع مُنشئ صغير.
ملاحظات الأداء
- GORM: مريح لكنه يضيف تحميلًا عبر التكرار/الإسقاط. مناسب للـ CRUD العادي؛ كن حذرًا من استعلامات N+1 (تفضيل
Joins
أوPreload
انتقائية). - Ent: الشيفرة المُولدة تتجنب التكرار؛ جيد لـ النماذج المعقدة. غالبًا أسرع من ORMs الثقيلة ذات السحر أثناء التشغيل.
- Bun: رقيق فوق
database/sql
؛ سريع، صريح، مثالي للعمليات الجماعية والنتائج الكبيرة. - sqlc: أداء SQL الخام تقريبًا مع الأمان أثناء التجميع.
نصائح عامة
- استخدم pgx كمحرك (v5) وcontext في كل مكان.
- تفضيل التوسيع (
COPY
، إدراج متعدد الصفوف) للتدفق العالي. - تحليل SQL:
EXPLAIN ANALYZE
، الفهارس، الفهارس المغطاة، تجنب الزيارات غير الضرورية. - إعادة استخدام الاتصالات؛ ضبط حجم المجموعة بناءً على الحمل.
تجربة المطور والبيئة
- GORM: أكبر مجتمع، العديد من الأمثلة/الإضافات؛ منحنى تعلم أصعب للأنماط المتقدمة.
- Ent: وثائق رائعة؛ خطوة إنشاء الشيفرة هي التحول الرئيسي في العقل؛ مثالي للفرق التي تعيد التصميم.
- Bun: استعلامات قابلة للقراءة وتنبؤية؛ مجتمع أصغر لكن نشط؛ ميزات Postgres رائعة.
- sqlc: تبعات تشغيلية قليلة؛ تتكامل جيدًا مع أدوات التحديث والـ CI؛ مثالي للفرق التي تفضل SQL النقي.
مميزات البارزة
- العلاقات والتحميل المبكر: جميعها تتعامل مع العلاقات؛ GORM (العلامات +
Preload
/Joins
)، Ent (الحواف +.With...()
)، Bun (Relation(...)
)، sqlc (تكتب التوصيلات). - التحديثات: GORM (تحديث تلقائي؛ كن حذرًا في الإنتاج)، Ent (SQL مُولدة/الاختلافات)، Bun (
bun/migrate
)، sqlc (أدوات خارجية). - التصحيحات/التوسع: GORM (التصحيحات/الإضافات)، Ent (التصحيحات/الوسيط + النماذج/الشيفرة)، Bun (تصحيحات استعلام وسائط + SQL نقي سهل)، sqlc (دمج في طبقة التطبيق).
- النصوص/المصفوفات (Postgres): Bun و GORM لهما مساعدين جيدين؛ Ent/sqlc تتعامل عبر أنواع مخصصة أو SQL.
متى تختار ماذا
- اختر GORM إذا كنت ترغب في الراحة القصوى، الميزات الغنية، والتطوير السريع لتطبيقات CRUD التقليدية.
- اختر Ent إذا كنت تفضل الأمان أثناء التجميع، النماذج صريحة، والصيانة طويلة الأمد في الفرق الكبيرة.
- اختر Bun إذا كنت ترغب في الأداء والأسئلة SQL صريحة مع ميزات ORM حيث تساعد.
- اختر sqlc إذا كنت أنت وفريقك تفضلون SQL النقي مع روابط Go مُحصنة نوعًا بدون تحميل أثناء التشغيل.
docker-compose.yml
أدنى لـ 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