من ذكريات أبو عمر: ليلة إطلاق التطبيق البطيء
أذكرها وكأنها البارحة. كنا على وشك إطلاق تطبيق جديد لعميل مهم، تطبيق اجتماعي بسيط. الفريق كله كان متحمس، “سهرانين” لآخر الليل نضع اللمسات الأخيرة. أنا كنت مسؤولاً عن جزء من الواجهة الخلفية (Backend) وفريق الواجهة الأمامية (Frontend) كان يعمل على قدم وساق لإنهاء واجهات التطبيق على الموبايل.
بدأنا الاختبارات النهائية على شبكة 3G بطيئة لمحاكاة أسوأ الظروف… وكانت الكارثة. الصفحة الرئيسية، التي تعرض قائمة بسيطة من المستخدمين مع صورهم وأسمائهم، كانت تستغرق 10 ثوانٍ كاملة للتحميل! شعور الإحباط انتشر في الغرفة أسرع من النار في الهشيم.
زميلي في الواجهة الأمامية، شاب متحمس اسمه خالد، جاءني وعلى وجهه كل علامات اليأس وقال: “يا أبو عمر، شو القصة؟ نقطة النهاية (endpoint) اللي بترجع المستخدمين بتبعتلي “جريدة” كاملة عن كل مستخدم! تاريخ ميلاده، آخر 100 تعليق كتبها، قائمة متابعينه… وأنا كل اللي بدي ياه هو الاسم والصورة! التطبيق “متخوم” بالبيانات ومتنا ثقل.”
في المقابل، صفحة الملف الشخصي كانت تعاني من مشكلة عكسية. لعرض صفحة مستخدم واحد بكل تفاصيلها (معلوماته، آخر 5 مقالات، وعدد الإعجابات على كل مقال)، كان خالد يضطر لإرسال 7 طلبات مختلفة لواجهة برمجة التطبيقات (API)! طلب للمستخدم، طلب للمقالات، ثم 5 طلبات أخرى للحصول على تفاصيل الإعجابات لكل مقال. كانت الواجهة “جائعة” ودائماً تطلب المزيد، مما يخلق شلالاً من الطلبات التي تقتل الأداء.
تلك الليلة، أدركنا أن طريقتنا التقليدية باستخدام REST APIs، رغم قوتها، كانت تخلق لنا هذه المعضلة: إما أن نرسل بيانات أكثر من اللازم (Over-fetching) أو نجبر العميل على طلب البيانات على دفعات (Under-fetching). هنا بدأت رحلتنا للبحث عن حل، حل كان اسمه GraphQL.
ما هي مشكلة الواجهات “الجائعة” و”المتخمة”؟
قبل أن نغوص في أعماق GraphQL، دعونا نفصّل المشكلتين اللتين واجهتنا مع بنية REST التقليدية. بنية REST تعتمد على مفهوم “الموارد” (Resources). لديك مورد للمستخدمين (`/users`)، ومورد للمقالات (`/posts`)، وهكذا. كل مورد له نقطة نهاية خاصة به، وهذا التصميم هو أصل المشكلة.
الـ Over-fetching: عندما تكون الواجهة “متخمة” بالبيانات
الـ Over-fetching يعني أن الواجهة الخلفية ترسل بيانات أكثر بكثير مما تحتاجه الواجهة الأمامية لعرض ميزة معينة. هذا بالضبط ما حدث معنا في قصة إطلاق التطبيق.
تخيل أن لديك نقطة نهاية في REST API لجلب تفاصيل مستخدم:
GET /api/users/123
قد تبدو الاستجابة (Response) بهذا الشكل:
{
"id": "123",
"name": "أبو عمر",
"username": "abu_omar_dev",
"email": "abu.omar@example.com",
"bio": "مبرمج فلسطيني يحب القهوة والكود النظيف.",
"birthDate": "1985-01-15T00:00:00.000Z",
"address": {
"street": "شارع المبرمجين",
"city": "القدس",
"country": "فلسطين"
},
"followers_count": 5000,
"following_count": 150,
"posts": [
{ "id": "p1", "title": "مقال 1" /* ... وتفاصيل أخرى كثيرة */ },
{ "id": "p2", "title": "مقال 2" /* ... وتفاصيل أخرى كثيرة */ }
// ... 98 مقالاً آخر
]
}
الآن، تخيل أن واجهة الموبايل تريد فقط عرض قائمة بأسماء المستخدمين وصورهم الرمزية (التي لم نضعها حتى في هذا المثال!). هي لا تحتاج لكل هذه التفاصيل. هذه البيانات الإضافية تستهلك باقة الإنترنت لدى المستخدم، تبطئ تحميل التطبيق، وتزيد العبء على الخادم والمعالج في جهاز المستخدم. الواجهة هنا “متخمة” حد الاختناق.
الـ Under-fetching: عندما تكون الواجهة “جائعة” للمزيد
على النقيض تماماً، يحدث الـ Under-fetching عندما لا توفر نقطة نهاية واحدة كل البيانات التي تحتاجها الواجهة الأمامية، مما يجبرها على إرسال طلبات متعددة ومتتالية للحصول على كل ما تحتاجه. وهذا ما نسميه أحياناً بمشكلة “N+1 Requests”.
لنعُد لمثال صفحة الملف الشخصي التي تحتاج لعرض:
- معلومات المستخدم الأساسية.
- آخر 3 مقالات كتبها.
- قائمة بأسماء أول 5 معلقين على كل مقال.
باستخدام REST، قد يبدو تسلسل الطلبات كالتالي:
GET /api/users/123(للحصول على معلومات المستخدم)GET /api/users/123/posts?limit=3(للحصول على المقالات)GET /api/posts/p1/comments?limit=5(للحصول على تعليقات المقال الأول)GET /api/posts/p2/comments?limit=5(للحصول على تعليقات المقال الثاني)GET /api/posts/p3/comments?limit=5(للحصول على تعليقات المقال الثالث)
لاحظت الكارثة؟ 5 طلبات لعرض صفحة واحدة فقط! كل طلب له زمن استجابة خاص به (latency)، وهذا يخلق تجربة استخدام بطيئة ومتقطعة. الواجهة هنا “جائعة” وتظل تطلب المزيد والمزيد من الخادم.
GraphQL يدخل المشهد: كيف غيّر قواعد اللعبة؟
GraphQL ليست مكتبة أو إطار عمل، بل هي “لغة استعلام” (Query Language) للـ API الخاص بك، ونمط معماري جديد. الفكرة عبقرية في بساطتها: بدلاً من أن يقرر الخادم شكل البيانات وحجمها، يقوم العميل (الواجهة الأمامية) بوصف البيانات التي يحتاجها بدقة، ويرد الخادم بنفس الشكل المطلوب، لا أكثر ولا أقل.
بنية طلب GraphQL: اسأل وتحصل على ما تريد بالزبط
مع GraphQL، كل شيء يبدأ بالطلب (Query). الواجهة الأمامية تكتب طلباً يحدد الحقول التي تريدها. لنحل المشكلتين السابقتين باستخدام GraphQL.
لحل مشكلة الـ Over-fetching (الواجهة المتخمة):
واجهة الموبايل التي تريد فقط اسم المستخدم وصورته الرمزية، سترسل الطلب التالي:
query GetUserForList {
user(id: "123") {
name
avatarUrl
}
}
وستكون الاستجابة من الخادم مطابقة تماماً لما طُلب:
{
"data": {
"user": {
"name": "أبو عمر",
"avatarUrl": "https://example.com/avatars/abu_omar.jpg"
}
}
}
لاحظ الجمال هنا! لا بيانات زائدة، لا إهدار للموارد. الواجهة الأمامية حصلت على ما تريده بالزبط.
لحل مشكلة الـ Under-fetching (الواجهة الجائعة):
صفحة الملف الشخصي المعقدة التي كانت تحتاج 5 طلبات، يمكنها الآن الحصول على كل بياناتها في طلب واحد فقط:
query GetUserProfilePage {
user(id: "123") {
name
bio
posts(first: 3) {
title
content
comments(first: 5) {
body
author {
name
}
}
}
}
}
والاستجابة ستكون بنية متداخلة تحتوي على كل ما طلبناه في مرة واحدة. قل وداعاً لشلال الطلبات!
نقطة نهاية واحدة (Single Endpoint) تحكم كل شيء
من أجمل ما في GraphQL هو أنه، في العادة، يعرض كل قدراته عبر نقطة نهاية واحدة فقط (مثلاً /graphql). كل طلبات القراءة (Queries) والتعديل (Mutations) تُرسل إلى هذا العنوان. هذا يبسط الأمور بشكل كبير على فريق الواجهة الأمامية، فلم يعودوا بحاجة لحفظ وإدارة عشرات نقاط النهاية المختلفة. كل ما يحتاجونه هو بناء الطلب الصحيح وإرساله.
نصائح من مطبخ أبو عمر: متى وكيف تستخدم GraphQL؟
بعد سنوات من العمل مع هذه التقنية، تعلمت بعض الدروس التي أحب أن أشاركها معكم يا جماعة.
هل GraphQL هو الحل لكل المشاكل؟
بكل صراحة، لا. GraphQL ليس الرصاصة الفضية التي تحل كل شيء. REST ما زال قوياً جداً ومناسباً في كثير من الحالات، خصوصاً في الواجهات البسيطة الموجهة للموارد (resource-oriented) أو عند بناء خدمات مصغرة (microservices) تتواصل فيما بينها.
نصيحة أبو عمر: GraphQL يلمع ويتألق عندما يكون لديك:
- عملاء متعددون بمتطلبات بيانات مختلفة (تطبيق ويب، تطبيق موبايل، ساعة ذكية).
- بنية بيانات معقدة ومترابطة (مثل شبكة اجتماعية، منصة تجارة إلكترونية).
- حاجة ماسة لتحسين أداء الواجهة الأمامية وتقليل عدد طلبات الشبكة.
نصيحة عملية: ابدأ صغيراً ومغلفاً
لست مضطراً لإعادة كتابة كل واجهاتك الخلفية من الصفر. أحد أقوى الأنماط هو بناء “طبقة GraphQL” فوق واجهات REST API الحالية. يمكنك إنشاء خادم GraphQL يعمل كواجهة موحدة (façade) تتحدث مع خدمات REST القديمة في الخلفية. هذا يمنح فريق الواجهة الأمامية كل قوة GraphQL دون الحاجة لتغيير كبير في البنية التحتية الحالية. أدوات مثل Apollo Server تجعل هذا الأمر سهلاً جداً.
فكر في “Schema” أولاً
قلب أي تطبيق GraphQL هو الـ Schema (المخطط). هو العقد الرسمي بين الواجهة الأمامية والخلفية. يصف كل أنواع البيانات المتاحة والعمليات التي يمكن إجراؤها. قبل كتابة أي كود، يجب أن يجلس فريقا الواجهة الأمامية والخلفية معاً ويتفقا على شكل الـ Schema.
هذا المخطط يصبح المصدر الوحيد للحقيقة (Single Source of Truth). إليك مثال بسيط على شكل الـ Schema:
# يصف المستخدم
type User {
id: ID!
name: String!
email: String
posts: [Post!]
}
# يصف المقال
type Post {
id: ID!
title: String!
author: User!
comments: [Comment!]
}
# يصف التعليق
type Comment {
id: ID!
body: String!
author: User!
}
# يصف الطلبات المتاحة
type Query {
user(id: ID!): User
posts: [Post!]
}
هذا المخطط ليس مجرد توثيق، بل هو مخطط حي يمكن للأدوات استخدامه لتوليد الكود والتحقق من صحة الطلبات تلقائياً.
الخلاصة: هل حان وقت التخلي عن REST؟ 🚀
الجواب هو لا. السؤال الأصح هو: “متى أضيف GraphQL إلى صندوق أدواتي؟”. REST قدم لنا الكثير وما زال أداة رائعة. لكن عندما تبدأ واجهاتك بالمعاناة من “التخمة” و”الجوع”، وعندما يصبح التنسيق بين فرق العمل كابوساً، فإن GraphQL يقدم حلاً أنيقاً وفعالاً يعيد القوة والمرونة للواجهة الأمامية.
لقد أنقذنا من جحيم الطلبات المتعددة والبيانات غير المرغوب فيها، وسمح لنا ببناء تطبيقات أسرع وأكثر كفاءة. نصيحتي الأخيرة لكم يا جماعة: لا تخافوا من التجربة. ابدأوا بمشروع جانبي صغير، جربوا بناء واجهة GraphQL بسيطة، وشاهدوا الفرق بأنفسكم. التجربة هي خير برهان، وهي التي تحول المبرمج من مجرد تابع إلى قائد ومبتكر.
بالتوفيق في رحلتكم البرمجية!