يا جماعة الخير، السلام عليكم ورحمة الله.
بتذكر قبل كم سنة، كنا شغالين على مشروع لوحة تحكم (Dashboard) ضخمة لعميل مهم. الفكرة كانت بسيطة على الورق: صفحة رئيسية بتعرض للمستخدم معلومات حسابه، آخر منشوراته، قائمة أصدقائه، وآخر الإشعارات. “شغل بسيط” هيك حكينا لحالنا… يا ريت ضلينا ساكتين!
لما بلشنا شغل الواجهة الأمامية (Frontend)، اكتشفنا إنه عشان نجيب هالمعلومات “البسيطة”، لازم نحكي مع جيش من الـ Endpoints اللي جهزها فريق الواجهة الخلفية (Backend) باستخدام معمارية REST المعتادة:
- بدك اسم المستخدم وصورته؟ اطلب
GET /api/users/{id}. - بدك آخر 5 منشورات؟ اطلب
GET /api/users/{id}/posts?limit=5. - بدك قائمة الأصدقاء؟ اطلب
GET /api/users/{id}/friends. - والإشعارات؟ طبعاً الها endpoint لحالها:
GET /api/notifications?userId={id}.
النتيجة؟ الصفحة كانت بتاخد وقت طويل لتحمّل، وكنا زي اللي رايح جاي عالدكانة عشان يجيب كل غرض لحال. وفوق كل هاد، الـ endpoint تبع /api/users/{id} كان يرجّعلنا موسوعة عن حياة المستخدم من تاريخ ميلاده لعنوان بيته، مع إن كل اللي بدنا إياه هو اسمه وصورته! كل ما نطلب شغلة صغيرة من فريق الباك إند، زي مثلاً “ضيفولنا عدد اللايكات على كل منشور في الـ endpoint تبع المنشورات”، كانت تبدأ قصة طويلة عريضة واجتماعات وتعديلات ممكن تاخد أيام.
حسيت حالي بغرق في بحر من الطلبات والبيانات الزايدة، لحد ما واحد من الشباب في الفريق حكى كلمة غيّرت كل شي: “يا جماعة، شو رأيكم نجرّب GraphQL؟”. يومها، ما كنت بعرف إن هالكلمة رح تكون طوق النجاة من جحيم كنا عايشين فيه.
ما هو جحيم الـ Over-fetching والـ Under-fetching؟
قبل ما أحكيلكم عن الحل، خلوني أشرحلكم بالتفصيل عن “الجحيم” اللي كنا فيه. هدول المصطلحين هما أساس المشكلة مع معمارية REST التقليدية في التطبيقات المعقدة.
الـ Over-fetching: لما تجيب “كل الحارة” عشان تسلّم على واحد
الـ Over-fetching بيصير لما تطلب بيانات من الـ API، ويرجعلك الخادم (Server) بيانات أكثر بكثير من اللي بتحتاجها فعلياً في واجهة المستخدم. تخيل إنك بدك تعرض اسم المستخدم وصورته الشخصية بس.
بتروح تطلب من الـ API التقليدي:
GET /api/v1/users/123
والرد اللي بيجيك بكون عبارة عن ملف JSON ضخم زي هيك:
{
"id": 123,
"username": "abu_omar_dev",
"firstName": "عمر",
"lastName": "أحمد",
"email": "abu.omar@example.com",
"profilePicture": "https://example.com/path/to/image.jpg",
"dateOfBirth": "1985-05-20T00:00:00.000Z",
"address": {
"street": "شارع القدس",
"city": "نابلس",
"country": "فلسطين"
},
"phoneNumber": "+970-599-123456",
"createdAt": "2020-01-15T10:30:00.000Z",
"lastLogin": "2023-10-26T18:00:00.000Z",
"isActive": true
// ... وعشرات الحقول الأخرى
}
كل اللي كنت محتاجه هو حقلي username و profilePicture. لكنك استلمت كل هاي البيانات الزايدة. هذا يعني استهلاك أكبر للإنترنت (كارثة لمستخدمي باقات الموبايل)، وقت أطول في التحميل، ومجهود إضافي على المتصفح لمعالجة هاي البيانات. باختصار، هدر للموارد.
الـ Under-fetching: مشاوير “رايح-جاي” اللي ما بتخلص
هذا هو الوجه الآخر للمشكلة. الـ Under-fetching بيصير لما الـ endpoint الواحد ما يعطيك كل البيانات اللي بتحتاجها، فتضطر تعمل طلبات إضافية للحصول على الباقي. هاي هي مشكلة “المشاوير” اللي حكيت عنها في البداية.
في مثال لوحة التحكم تبعتنا، عشان نعرض الصفحة كاملة، كنا بنعمل سلسلة من الطلبات (Network Waterfall):
- الطلب الأول: جلب معلومات المستخدم الأساسية.
- الطلب الثاني: بعد ما يوصلنا id المستخدم، بنطلب منشوراته.
- الطلب الثالث: بنطلب قائمة أصدقائه.
- الطلب الرابع: بنطلب الإشعارات غير المقروءة.
كل طلب من هدول بيضيف وقت انتظار (latency)، والصفحة ما بتكتمل إلا بعد ما آخر طلب يرجع. هذا بيخلق تجربة مستخدم سيئة جداً، وبتحس الصفحة “بتقطّع” وهي بتحمّل.
وكيف دخلت ‘GraphQL’ على الخط وأنقذت الموقف؟
GraphQL هي لغة استعلام (Query Language) للـ API تبعك، تم تطويرها في فيسبوك عشان تحل هاي المشاكل بالتحديد. الفكرة عبقرية وبسيطة بنفس الوقت.
المبدأ بسيط: أنت تطلب، والخادم يُلبي… بالضبط!
بدل ما يكون عندك عشرات الـ endpoints وكل واحد بيرجع شكل بيانات ثابت، مع GraphQL بكون عندك endpoint واحد فقط (عادة /graphql). العميل (الـ Frontend) هو اللي بقرر شو البيانات اللي بده إياها بالزبط، عن طريق إرسال “استعلام” أو Query.
الأمر أشبه بالفرق بين البوفيه المفتوح (REST) وطلب قائمة طعام محددة (GraphQL). في البوفيه، ممكن تلاقي حالك حاطط بصحنك أكل أكثر من حاجتك. أما مع القائمة، أنت بتطلب اللي بدك إياه وبيجيك على قد طلبك، “لا زيادة ولا نقصان”.
لنرجع لمثال لوحة التحكم. باستخدام GraphQL، فريق الواجهة الأمامية صار يقدر يجيب كل البيانات اللي بيحتاجها في طلب واحد فقط، بهذا الشكل:
query GetDashboardData {
user(id: "123") {
username
profilePicture
posts(last: 5) {
id
title
likesCount
}
friends(first: 10) {
id
name
profilePicture
}
notifications(unread: true) {
id
message
}
}
}
والأجمل من هيك؟ الرد اللي بيرجع من الخادم بكون بنفس شكل الطلب تماماً، بدون أي بيانات زايدة:
{
"data": {
"user": {
"username": "abu_omar_dev",
"profilePicture": "https://example.com/path/to/image.jpg",
"posts": [
{ "id": "p1", "title": "مقالتي عن GraphQL", "likesCount": 150 },
{ "id": "p2", "title": "نصائح في الذكاء الاصطناعي", "likesCount": 230 }
],
"friends": [
{ "id": "f1", "name": "خالد", "profilePicture": "..." }
],
"notifications": [
{ "id": "n1", "message": "علي علّق على منشورك" }
]
}
}
}
لاحظوا كيف حلينا مشكلة الـ Over-fetching والـ Under-fetching بضربة واحدة! طلبنا فقط البيانات اللي بدنا إياها، وحصلنا عليها كلها في طلب واحد.
نصائح من مطبخ أبو عمر: متى وكيف تستخدم GraphQL؟
بعد ما اشتغلنا على مشاريع كثيرة باستخدام GraphQL، تعلمت كم شغلة بحب أشاركها معكم.
1. لا ترمِ REST في سلة المهملات فوراً!
GraphQL مش حل سحري لكل المشاكل. معمارية REST ما زالت ممتازة جداً ومناسبة لكثير من الحالات، خصوصاً في الخدمات المصغرة (Microservices) البسيطة أو الـ APIs اللي بتتعامل مع “موارد” واضحة ومحددة. إذا كان تطبيقك عبارة عن عمليات CRUD بسيطة (Create, Read, Update, Delete) على منتجات مثلاً، يمكن REST يكون أسرع وأسهل في التطوير. “مش كل إشي بده مدفع يا جماعة، أحياناً المسدس بكفّي”.
2. ابدأ صغيراً: GraphQL فوق REST
أجمل ما في الموضوع أنك مش مضطر تعيد كتابة كل الباك إند تبعك. من الاستراتيجيات الناجحة جداً هي بناء طبقة GraphQL (GraphQL Layer) فوق الـ REST APIs الموجودة عندك. بهذه الطريقة، الـ GraphQL server تبعك بيستقبل الـ Query من العميل، وبعدين هو اللي “بيحكي” مع كل الـ REST endpoints القديمة وبجمع البيانات وبرجعها للعميل بالشكل المطلوب. هاي طريقة ممتازة للانتقال التدريجي.
3. فكّر في التخزين المؤقت (Caching)
التخزين المؤقت في GraphQL مختلف شوي عن REST. في REST، كنا بنخزن الردود بناءً على الرابط (URL) تبع الطلب، مثلاً GET /api/users/123. أما في GraphQL، كل الطلبات بتروح على نفس الـ endpoint وهو POST /graphql.
الحل يكمن في مكتبات الـ client-side مثل Apollo Client أو urql، فهي ذكية كفاية لتفهم الردود وتخزنها بناءً على نوع البيانات والـ ID تبعها. هذا بيعطي caching فعال جداً على مستوى التطبيق.
نصيحة خبير: في الباك إند، احذر من مشكلة اسمها “N+1 Query”. لحلها، استخدم نمط اسمه
DataLoader. هو عبارة عن أداة بتجمع كل الطلبات المتشابهة في طلب واحد كبير لقاعدة البيانات، وهذا بحسّن الأداء بشكل خيالي.
4. أمّن الـ Endpoint الخاص بك
لأن GraphQL مرنة جداً، ممكن مستخدم خبيث يبعت طلب معقد جداً ومتشعب (deeply nested query) بهدف إنه يستهلك كل موارد الخادم ويعطله.
لهيك، لازم تطبق آليات حماية مثل:
- تحديد عمق الطلب (Query Depth Limiting): منع الطلبات اللي بتتشعب أكثر من حد معين.
- تحليل تعقيد الطلب (Query Complexity Analysis): إعطاء “تكلفة” لكل حقل في الـ Schema، ورفض أي طلب بتتجاوز تكلفته الإجمالية حداً معيناً.
- تحديد مهلة زمنية (Timeout): إيقاف أي طلب بياخد وقت أطول من اللازم.
الخلاصة: هل GraphQL هو المستقبل؟ 🚀
بالنسبة لي ولفريقي، GraphQL ما كانت مجرد تقنية جديدة، بل كانت نقلة نوعية في طريقة تفكيرنا وتطويرنا للتطبيقات. أعطتنا المرونة والسرعة، وفصلت الاعتمادية بين فريق الواجهة الأمامية والخلفية، وسمحت لكل فريق يبدع في مجاله.
هل هي بديل كامل لـ REST؟ لا أعتقد. لكل أداة مكانها وزمانها. لكن في عالم التطبيقات الحديثة اللي واجهاتها معقدة ومتشابكة (مثل تطبيقات الموبايل، Single-Page Applications)، أثبتت GraphQL أنها أداة لا يمكن الاستغناء عنها.
نصيحتي الأخيرة إلك: لا تخاف تجرب. ابدأ بمشروع جانبي صغير، ابنِ GraphQL API بسيط، وشوف الفرق بنفسك. أفضل طريقة للتعلم هي بالممارسة. ويا سيدي، صحتين وعافية على قلوبكم البرمجية! 😉