قصة “جبل البيانات” الذي كاد أن يدفن تطبيقي
يا جماعة الخير، السلام عليكم. معكم أخوكم أبو عمر.
قبل كم سنة، كنت شغال على تطبيق جوال لمشروع تجارة إلكترونية. التطبيق كان بسيط بفكرته: يعرض قائمة منتجات، والمستخدم يكبس على منتج، فيشوف تفاصيله. الواجهة الرئيسية للتطبيق كان لازم تعرض “كروت” (Cards) للمنتجات الأكثر مبيعاً. كل كرت كان بحاجة لثلاث معلومات فقط لا غير: اسم المنتج، صورته الرئيسية، وسعره.
الشباب في فريق الواجهات الخلفية (Backend) كانوا عاملين شغل مرتب ومجهزين لنا واجهة برمجة تطبيقات (API) من نوع REST. عشان أجيب المنتجات، كنت أطلب من نقطة النهاية (Endpoint) التالية: /api/products.
المشكلة وين؟ المشكلة إنه هاي النقطة كانت ترجعلي “جبل” من البيانات لكل منتج. كانت ترجعلي الاسم والسعر والصورة، ومعهم: كل صور المنتج (مش بس الرئيسية)، ووصف المنتج الكامل المكون من 500 كلمة، وآراء المشترين كلهم، وأبعاد المنتج، ووزنه، والكمية المتوفرة في المخزن، وتاريخ إضافته، واسم الموظف اللي أضافه… قائمة ما بتخلص! يا دوب الشبكة عندي بالبيت 3G، والمستخدم النهائي حالته مش راح تكون أحسن.
كنت أشوف التطبيق وهو “بِفحّط” عشان يحمّل هاي الشاشة البسيطة. كل طلب للشبكة كان يجيب معه عشرات الكيلوبايتات من البيانات اللي أنا مش بحاجتها إطلاقاً، وكنت أرميها في الزبالة (برمجياً طبعاً). هذا الأداء البطيء كان محبطاً جداً، وشعرت إنه في إشي غلط جوهري في الطريقة اللي بنشتغل فيها. من هنا بدأت رحلتي للبحث عن حل، وهناك التقيت بـ GraphQL.
ما هو “الإفراط في جلب البيانات” (Over-fetching)؟ ولماذا هو كارثة صامتة؟
المشكلة اللي واجهتها الها اسم تقني مشهور: Over-fetching أو “الإفراط في جلب البيانات”.
ببساطة، هي حالة تحدث عندما يقوم الخادم (Server) بإرسال بيانات أكثر مما يحتاجه العميل (Client) فعلياً. في عالم REST API التقليدي، هذا الأمر شائع جداً لأن نقاط النهاية (Endpoints) مصممة لتكون “مقاس واحد يناسب الجميع”.
مثال على Over-fetching في REST API
لنفترض أن لدينا نقطة النهاية /api/users/123 التي ترجع معلومات مستخدم. الواجهة الأمامية (Frontend) تحتاج فقط اسم المستخدم وصورته الشخصية لعرضها في رأس الصفحة. لكن، انظر ماذا يعيد الخادم:
{
"id": "123",
"username": "abu_omar_dev",
"email": "abu.omar@example.com",
"firstName": "عمر",
"lastName": "أحمد",
"profilePictureUrl": "https://.../pic.jpg",
"bio": "مبرمج فلسطيني أحب القهوة والكود...",
"dateOfBirth": "1985-01-15",
"address": {
"street": "شارع القدس",
"city": "رام الله",
"country": "فلسطين"
},
"lastLogin": "2023-10-26T10:00:00Z",
"purchaseHistory": [
{ "orderId": "abc", "amount": 100, "date": "..." },
{ "orderId": "def", "amount": 50, "date": "..." }
],
"preferences": {
"theme": "dark",
"notifications": true
}
}
العميل كان بحاجة فقط لـ username و profilePictureUrl. كل ما تبقى هو بيانات مهدرة تسببت في:
- بطء في تحميل التطبيق: حجم أكبر للبيانات يعني وقتاً أطول للتحميل، خاصة على شبكات الجوال البطيئة.
- استهلاك باقة الإنترنت للمستخدم: أنت تجبر المستخدم على تحميل بيانات لا يراها ولا يستفيد منها.
- زيادة الحمل على الخادم: الخادم يبذل جهداً في جلب بيانات من قاعدة البيانات وتحويلها إلى JSON، ليتم تجاهلها في النهاية.
- ارتفاع تكاليف الاستضافة: المزيد من استهلاك البيانات (Bandwidth) يعني فاتورة أعلى في نهاية الشهر.
محاولاتي اليائسة للالتفاف على المشكلة مع REST
طبعاً، كمبرمجين، ما بنستسلم بسهولة. حاولت أنا والفريق إيجاد حلول ضمن عالم REST نفسه.
الحل الأول: بناء نقاط نهاية مخصصة
كان أول حل خطر ببالنا هو أن نطلب من فريق الواجهات الخلفية إنشاء نقطة نهاية جديدة ومخصصة، مثلاً: /api/users/123/summary. هذه النقطة تعيد فقط الاسم والصورة.
المشكلة: هذا الحل سريع في البداية، ولكنه يتحول إلى كابوس مع نمو التطبيق. بعد فترة، احتجنا لعرض اسم المستخدم وتاريخ ميلاده في صفحة أخرى، فهل نطلب نقطة نهاية جديدة /api/users/123/birthday-info؟ وماذا عن التطبيق على الويب الذي يحتاج بيانات مختلفة؟
النتيجة كانت “انفجار” في عدد نقاط النهاية، وأصبح الحفاظ عليها وصيانتها أمراً معقداً جداً. أصبح فريق الـ Backend غارقاً في طلبات إنشاء “شغلات صغيرة” للـ Frontend.
الحل الثاني: استخدام معاملات الاستعلام (Query Parameters)
الحل الآخر كان أكثر مرونة. اتفقنا على استخدام معاملات الاستعلام لتحديد الحقول المطلوبة، هكذا:
/api/users/123?fields=username,profilePictureUrl
هذا الحل أفضل بكثير، لكنه أيضاً له مشاكله:
- غير قياسي: لا توجد طريقة موحدة للقيام بذلك في عالم REST. كل فريق يخترع طريقته الخاصة (
fields=,select=,props=). - معقد في البيانات المتداخلة: كيف تطلب حقلاً محدداً من كائن متداخل؟ مثلاً، كيف تطلب فقط مدينة المستخدم (city) من داخل كائن العنوان (address)؟ يصبح شكل الطلب معقداً وغير مقروء.
- عبء على الـ Backend: يتطلب من مطوري الواجهات الخلفية كتابة منطق برمجي معقد لتحليل هذه المعاملات وبناء الاستجابة ديناميكياً.
وظهر النور… مرحباً GraphQL!
بينما كنت غارقاً في هذه المشاكل، نصحني صديق لي بتجربة تقنية اسمها GraphQL. في البداية كنت متشككاً، “كمان تقنية جديدة نتعلمها؟”. لكن بعد أن قرأت عنها قليلاً، أدركت أنها مصممة خصيصاً لحل هذه المشكلة.
GraphQL هي لغة استعلام (Query Language) لواجهات برمجة التطبيقات، وبيئة تشغيل من طرف الخادم لتنفيذ هذه الاستعلامات باستخدام نظام أنواع (Type System) تحدده أنت لبياناتك.
انسوا كل المصطلحات المعقدة. الفكرة بسيطة جداً: العميل (Client) يطلب بالضبط ما يحتاجه، ولا شيء أكثر.
أفضل تشبيه هو الفرق بين البوفيه المفتوح (REST) والمطعم الذي تطلب فيه من القائمة (GraphQL). في البوفيه، أنت مجبر على أخذ الصحن كاملاً كما هو معروض، حتى لو كنت تريد قطعة صغيرة منه. أما في المطعم، فأنت تفتح القائمة، وتختار “طبق الحمص” و”صحن السلطة” فقط، ويأتيك ما طلبته بالضبط.
كيف تعمل GraphQL على أرض الواقع؟
دعنا نعد إلى مثال المستخدم الذي نحتاج منه فقط الاسم والصورة الشخصية. مع GraphQL، لا يوجد نقاط نهاية متعددة. هناك نقطة نهاية واحدة فقط (غالباً /graphql).
الطلب (The Query)
العميل (تطبيق الجوال) يرسل طلباً (Query) يصف فيه البيانات التي يريدها. شكل الطلب يشبه JSON ولكن بدون قيم:
query GetUserHeader {
user(id: "123") {
username
profilePictureUrl
}
}
لاحظ جمال وبساطة الطلب. كأنك تقول: “يا سيرفر، من فضلك، ابحث عن المستخدم الذي يحمل المعرّف 123، ومنه أعطني فقط اسم المستخدم (username) ورابط صورته الشخصية (profilePictureUrl)”.
الاستجابة (The Response)
الخادم الذي يفهم GraphQL سيقرأ هذا الطلب، ويجلب البيانات المطلوبة فقط، ثم يعيد استجابة JSON تتطابق تماماً مع شكل الطلب:
{
"data": {
"user": {
"username": "abu_omar_dev",
"profilePictureUrl": "https://.../pic.jpg"
}
}
}
قارن هذه الاستجابة الصغيرة والنظيفة بـ “جبل البيانات” الذي عرضته في البداية. لا إفراط في الجلب، لا بيانات مهدرة، لا استهلاك زائد للشبكة. المشكلة حُلّت بأناقة وكفاءة.
ليست مجرد حل لمشكلة واحدة! قوة GraphQL الخفية
مع الوقت، اكتشفت أن حل مشكلة الـ Over-fetching كان مجرد البداية. GraphQL لديها قوى خارقة أخرى:
- حل مشكلة الـ Under-fetching: هذه هي عكس المشكلة الأولى، وتعني أنك تحتاج لعمل عدة طلبات (multiple requests) لجلب كل البيانات التي تريدها. مثلاً، في REST، قد تحتاج لطلب
/users/123ثم/users/123/postsلجلب المستخدم ومنشوراته. مع GraphQL، يمكنك طلب كل ذلك في طلب واحد:query GetUserWithPosts { user(id: "123") { username posts(last: 5) { title createdAt } } } - نظام الأنواع (Schema & Types): GraphQL تعتمد على “مخطط” أو Schema قوي. هذا المخطط يعمل كـ “كتالوج” دقيق للـ API، يصف كل البيانات المتاحة وكيفية طلبها. هذا يوفر مصدراً واحداً للحقيقة (Single Source of Truth) بين فريق الواجهة الأمامية والخلفية، ويمنع الكثير من الأخطاء وسوء الفهم.
نصائح من مطبخ أبو عمر 👨🍳
بعد سنوات من استخدام GraphQL في مشاريع مختلفة، هذه بعض النصائح العملية من خبرتي:
- ليست حلاً سحرياً لكل شيء: GraphQL تتألق في التطبيقات المعقدة التي لديها عملاء متعددون (ويب، جوال، أجهزة أخرى) بمتطلبات بيانات مختلفة. للمشاريع البسيطة جداً أو للخدمات المصغرة (microservices) التي تتواصل داخلياً، قد تكون REST API أبسط وأسرع في التنفيذ.
- ابدأ صغيراً: لست مضطراً لإعادة كتابة كل شيء. يمكنك بناء طبقة GraphQL فوق واجهات REST API الموجودة لديك. هذا يمنحك فوائد GraphQL للعملاء الجدد دون الحاجة لهدم البنية التحتية القديمة.
- استخدم الأدوات المساعدة: مجتمع GraphQL ضخم ونشط. أدوات مثل Apollo (Client & Server) و Relay تجعل العمل مع GraphQL أسهل وأكثر إنتاجية. لا تحاول إعادة اختراع العجلة.
- انتبه للأمان: القوة الكبيرة تأتي مع مسؤولية كبيرة. بما أن العميل يمكنه طلب ما يشاء، قد يقوم ببناء طلبات معقدة جداً ومتداخلة تسبب إرهاقاً للخادم (Denial of Service). تأكد من تطبيق تقنيات مثل تحديد عمق الطلب (Query Depth Limiting) وتحليل تكلفة الطلب (Query Cost Analysis).
الخلاصة: هل حان وقت التغيير؟
بالنسبة لي ولفريقي، كان الانتقال إلى GraphQL نقلة نوعية. لقد أعادت السيطرة إلى يد مطور الواجهة الأمامية، وحسّنت أداء تطبيقاتنا بشكل جذري، وجعلت التعاون بين الفرق أكثر سلاسة. لم نعد نسمع جملة “ممكن تعملي endpoint جديد عشان هاي الشغلة الصغيرة؟”.
GraphQL ليست حرباً ضد REST. فـ REST لا تزال تقنية عظيمة ومناسبة جداً في كثير من الحالات. لكن GraphQL هي أداة متخصصة وقوية جداً لحل فئة معينة من المشاكل التي أصبحت شائعة جداً في عالم تطوير التطبيقات الحديثة.
نصيحتي لك: لا تخف من التجربة. ابدأ بمشروع جانبي صغير، جرب بناء واجهة GraphQL بسيطة، وشاهد الفرق بنفسك. قد تكون هي الأداة التي كنت تبحث عنها تماماً كما كانت بالنسبة لي. شدوا حيلكم يا جماعة! 💪