GraphQL مقابل REST في Go: ماذا تختار في 2025

مدرب بايثون أونلاين للمبتدئين

تعلم Python بسهولة دون إرهاق نظري. حل مهام عملية مع التحقق التلقائي واكتب الكود مباشرة في المتصفح.

ابدأ الدورة

GraphQL مقابل REST في Go: مبارزة العمالقة، أو كيف لا تخطئ في الاختيار

\n\n

إذا كنت تكتب بلغة Go لمدة ستة أشهر على الأقل، فمن المحتمل أنك واجهت النقاش الأبدي: REST أم GraphQL؟ الأول هو "حصان العمل" القديم الجيد، والثاني هو "سيارة رياضية" عصرية مليئة بالخيارات. لكن في عالم Go، حيث الأداء والبساطة هما الدين، ليس الاختيار واضحًا دائمًا.

\n\n

في هذه المقالة، لن نقوم فقط بسرد الإيجابيات والسلبيات. سنكتب كودًا حقيقيًا بلغة Go، ونقيس وقت التنفيذ، ونحلل متى يكون GraphQL طلقة في القدم، ومتى يكون ممرًا غشًا إلى عالم واجهات API الفعالة. هيا بنا!

\n\n

REST في Go: الاستقرار والقدرة على التنبؤ

\n\n

REST يشبه المطرقة القديمة المختبرة. إنها لا تلمع، لكنها تدق المسامير بشكل مثالي. في Go، تُكتب واجهات REST API باستخدام net/http أو أطر عمل خفيفة مثل gin و chi. دعنا نلقي نظرة على مثال بسيط:

\n\n
package main\n\nimport (\n    "encoding/json"\n    "net/http"\n)\n\ntype User struct {\n    ID   int    `json:"id"`\n    Name string `json:"name"`\n}\n\nfunc getUserHandler(w http.ResponseWriter, r *http.Request) {\n    user := User{ID: 1, Name: "أليكسي"}\n    w.Header().Set("Content-Type", "application/json")\n    json.NewEncoder(w).Encode(user)\n}\n\nfunc main() {\n    http.HandleFunc("/api/user", getUserHandler)\n    http.ListenAndServe(":8080", nil)\n}
\n\n

جميل؟ نعم. بسيط؟ جدًا. لكن ماذا لو كان الواجهة الأمامية تحتاج فقط إلى اسم المستخدم، ونحن نسحب الكائن بأكمله؟ في REST، يُسمى هذا over-fetching - زيادة في البيانات. وفي Go، حيث اعتدنا التحكم في كل بايت، قد يكون هذا مزعجًا.

\n\n

مشكلة N+1 في REST

\n\n

لنفترض أن لدينا قائمة بالطلبات، ولكل طلب نحتاج اسم المستخدم. في REST، إما أن نقوم بعمل N من الطلبات (بطيء)، أو ننشئ نقطة نهاية منفصلة /api/orders-with-users (الكثير من نقاط النهاية). يتعامل Go مع هذا من خلال استعلامات join في قاعدة البيانات، ولكن على مستوى API، لا يزال يبدو غير أنيق.

\n\n

GraphQL في Go: القوة والمرونة

\n\n

GraphQL يشبه السكين السويسري: نقطة نهاية واحدة، والعميل هو من يقرر البيانات التي يحتاجها. في Go، يوجد إطار عمل gqlgen (من 99designs) - يقوم بتوليد الكود من المخطط، وهذا يتماشى جدًا مع روح Go (كود أقل، عمل أكثر).

\n\n
// schema.graphql\ntype Query {\n    user(id: Int!): User\n}\n\ntype User {\n    id: Int!\n    name: String!\n    orders: [Order!]!\n}\n\ntype Order {\n    id: Int!\n    total: Float!\n}
\n\n

الآن التنفيذ بلغة Go:

\n\n
package graph\n\nimport (\n    "context"\n    "fmt"\n)\n\ntype Resolver struct{}\n\nfunc (r *Resolver) User(ctx context.Context, args struct{ ID int }) (*User, error) {\n    // في الواقع - استعلام لقاعدة البيانات\n    return &User{\n        ID:   args.ID,\n        Name: "ماريا",\n        Orders: []*Order{\n            {ID: 1, Total: 250.50},\n        },\n    }, nil\n}
\n\n

العميل يطلب فقط name - يحصل فقط على name. لا يوجد over-fetching. لكن هناك فارق بسيط: إذا طلب العميل orders لـ 10 مستخدمين، ولكل منهم 50 طلبًا - قد تنهار قاعدة البيانات. يُسمى هذا مشكلة N+1 في GraphQL، وفي Go يتم حلها باستخدام DataLoaders.

\n\n

DataLoader - المنقذ من N+1

\n\n
import "github.com/graph-gophers/dataloader"\n\ntype UserLoader struct {\n    *dataloader.Loader\n}\n\nfunc NewUserLoader() *UserLoader {\n    return &UserLoader{\n        Loader: dataloader.NewBatchedLoader(func(ctx context.Context, keys dataloader.Keys) []*dataloader.Result {\n            // تجميع الاستعلامات في قاعدة البيانات\n            userIDs := make([]int, len(keys))\n            for i, key := range keys {\n                userIDs[i] = key.Int()\n            }\n            users := batchFetchUsers(userIDs) // استعلام SQL واحد مع WHERE IN\n            results := make([]*dataloader.Result, len(ke                

المدونات

توصيات الكتب