لماذا Go هي اللغة المثالية لـ API؟
\n\nعند الحديث عن إنشاء APIs عالية التحميل، تحتل Go (Golang) بثقة مكانًا في القمة. طبيعتها المترجمة، والدعم المدمج للتوازي عبر goroutines، وبناء الجملة البسيط، تسمح بكتابة خدمات مصغرة سريعة وموثوقة. ولكن أي بروتوكول تختار لمشروعك القادم: REST الكلاسيكي، GraphQL المرن، أم gRPC فائق السرعة؟
\n\nفي هذه المقالة، لن نقتصر على سرد الاختلافات — بل سنكتب كودًا حقيقيًا بلغة Go لكل نهج، ونحلل اختبارات الأداء، ونقدم معايير واضحة للاختيار. هيا بنا!
\n\nREST API بلغة Go: الكلاسيكية المثبتة
\n\nيظل REST (Representational State Transfer) المعيار الفعلي لـ APIs العامة. تقدم Go مكتبة قياسية ممتازة net/http، بالإضافة إلى أطر عمل شائعة مثل gin و echo.
مثال لخادم REST بلغة Go النقية
\n\nلنبدأ بخدمة CRUD بسيطة لإدارة المهام. إليك كيف يبدو الحد الأدنى من REST API بلغة Go بدون تبعيات خارجية:
\n\npackage main\n\nimport (\n "encoding/json"\n "log"\n "net/http"\n "sync"\n)\n\ntype Task struct {\n ID int `json:"id"`\n Name string `json:"name"`\n}\n\nvar (\n tasks = []Task{{ID: 1, Name: "تعلم Go"}}\n mu sync.Mutex\n nextID = 2\n)\n\nfunc getTasks(w http.ResponseWriter, r *http.Request) {\n mu.Lock()\n defer mu.Unlock()\n w.Header().Set("Content-Type", "application/json")\n json.NewEncoder(w).Encode(tasks)\n}\n\nfunc createTask(w http.ResponseWriter, r *http.Request) {\n var task Task\n if err := json.NewDecoder(r.Body).Decode(&task); err != nil {\n http.Error(w, err.Error(), http.StatusBadRequest)\n return\n }\n mu.Lock()\n task.ID = nextID\n nextID++\n tasks = append(tasks, task)\n mu.Unlock()\n w.WriteHeader(http.StatusCreated)\n json.NewEncoder(w).Encode(task)\n}\n\nfunc main() {\n http.HandleFunc("/tasks", func(w http.ResponseWriter, r *http.Request) {\n switch r.Method {\n case http.MethodGet:\n getTasks(w, r)\n case http.MethodPost:\n createTask(w, r)\n default:\n http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)\n }\n })\n log.Println("REST API قيد التشغيل على :8080")\n log.Fatal(http.ListenAndServe(":8080", nil))\n}\n\n\nمزايا REST على Go:
\n- \n
- البساطة والقابلية للتنبؤ — كل نقطة نهاية تعيد بنية بيانات ثابتة \n
- قابلية تخزين مؤقت ممتازة عبر رؤوس HTTP \n
- دعم واسع للأدوات (Swagger, Postman) \n
العيوب: الإفراط في جلب البيانات (Over-fetching) ونقص الجلب (Under-fetching). إذا كان العميل يحتاج فقط إلى اسم المهمة، فسيظل يحصل على JSON كامل.
\n\nGraphQL على Go: مرونة الاستعلامات
\n\nيحل GraphQL مشكلة تكرار البيانات. يقرر العميل بنفسه الحقول التي يحتاجها. يقدم مجتمع Go مكتبة قوية gqlgen، والتي تولد الكود من المخطط.
مثال لخادم GraphQL مع gqlgen
\n\nأولاً، نحدد المخطط schema.graphql:
type Task {\n id: ID!\n name: String!\n}\n\ntype Query {\n tasks: [Task!]!\n}\n\ntype Mutation {\n createTask(name: String!): Task!\n}\n\n\nالآن، نشغل التوليد ونكتب المحللات (resolvers) بلغة Go:
\n\npackage main\n\nimport (\n "log"\n "net/http"\n "github.com/99designs/gqlgen/graphql/handler"\n "github.com/99designs/gqlgen/graphql/playground"\n)\n\n// نفترض أن الكود المُولَّد موجود بالفعل\ntype Resolver struct {\n tasks []Task\n mu sync.Mutex\n}\n\nfunc (r *Resolver) Tasks(ctx context.Context) ([]Task, error) {\n r.mu.Lock()\n defer r.mu.Unlock()\n return r.tasks, nil\n}\n\nfunc (r *Resolver) CreateTask(ctx context.Context, name string) (*Task, error) {\n r.mu.Lock()\n defer r.mu.Unlock()\n