يا هلا بيكم يا جماعة الخير. اسمي أبو عمر، مبرمج قضيت سنين طويلة من عمري بين الأكواد والخوارزميات، وشفت التقنيات بتطلع وبتنزل زي فصول السنة. اليوم بدي أحكيلكم قصة صارت معي ومع فريقي قبل كم سنة، قصة “ولّعت معي” وخلتني أدور على حل جذري لمشكلة كانت بتستنزف أداء تطبيقاتنا وموارد سيرفراتنا.
كنا شغالين على تطبيق موبايل لمتجر إلكتروني كبير. الواجهة الرئيسية للتطبيق كانت بسيطة وأنيقة: في الأعلى، ترحيب بالمستخدم مع صورته الشخصية، وفي المنتصف، قائمة بأحدث المنتجات، وفي الأسفل، لمحة سريعة عن آخر طلبين قام بهما المستخدم. تصميم جميل، أليس كذلك؟ لكن خلف هذا الجمال، كانت هناك فوضى تقنية حقيقية.
لتحميل هذه الواجهة البسيطة، كان تطبيق الموبايل المسكين مضطراً للقيام بثلاثة استدعاءات منفصلة لواجهة برمجة التطبيقات (API) من نوع REST:
GET /api/v1/users/meلجلب معلومات المستخدم.GET /api/v1/products?sort=newest&limit=5لجلب أحدث المنتجات.GET /api/v1/users/me/orders?limit=2لجلب آخر طلبين.
المصيبة ما كانت بس في عدد الطلبات، المصيبة الأكبر كانت في حجم البيانات. الاستدعاء الأول /api/v1/users/me كان يرجع كائن JSON ضخم فيه كل شيء عن المستخدم: اسمه، إيميله، عنوانه الكامل، تاريخ ميلاده، سجل طلباته بالكامل، قائمة أمنياته… كل شيء! بينما كل ما كنا نحتاجه في الواجهة الرئيسية هو الاسم وصورة البروفايل. كنا بنحمّل “شوال” بيانات عشان نستخدم منه “حبتين” بس. وعلى شبكات الموبايل البطيئة، كانت النتيجة كارثية: بطء في التحميل وتجربة مستخدم سيئة جدًا. هنا كانت البداية، بداية رحلتنا للبحث عن المنقذ: GraphQL.
ما هو جحيم الـ REST API الذي كنا نعيشه؟
قبل ما ندخل في الحل، خلوني أفصّلكم أكثر عن المشاكل اللي واجهتنا مع REST API التقليدية، وهي مشاكل شائعة جدًا في كثير من المشاريع.
مشكلة البيانات الزائدة (Over-fetching)
هذه كانت أكبر مشاكلنا. ببساطة، الـ Over-fetching يعني أن الـ API يرسل بيانات أكثر بكثير مما يحتاجه العميل (Client) فعليًا. في مثالنا، كنا نحتاج فقط اسم المستخدم وصورته، لكننا كنا نستقبل شيئًا كهذا:
{
"id": "user-123",
"name": "أبو عمر",
"email": "abu.omar@example.com",
"avatarUrl": "https://example.com/avatar.jpg",
"dateOfBirth": "1980-01-15T00:00:00.000Z",
"address": {
"street": "شارع القدس",
"city": "رام الله",
"country": "فلسطين",
"zipCode": "P602"
},
"fullOrderHistory": [
{ "orderId": "order-abc", "total": 99.99, "date": "..." },
{ "orderId": "order-def", "total": 45.50, "date": "..." },
// ... والمزيد من الطلبات
],
"wishlist": [
// ... قائمة طويلة من المنتجات
]
}
كل هذه البيانات الإضافية تستهلك من باقة الإنترنت للمستخدم، تزيد من وقت التحميل، وتضع عبئًا غير ضروري على السيرفر والشبكة. إنها هدر حقيقي للموارد.
مشكلة نقص البيانات والاستدعاءات المتعددة (Under-fetching)
هذه هي الوجه الآخر للعملة. الـ Under-fetching يعني أن نقطة النهاية (Endpoint) الواحدة لا توفر كل البيانات التي تحتاجها، مما يجبرك على إجراء استدعاءات متعددة لتجميع كل المعلومات اللازمة لعرض واجهة واحدة.
وهذا بالضبط ما كان يحدث معنا. لعرض الشاشة الرئيسية، كنا بحاجة إلى:
- بيانات المستخدم (استدعاء 1).
- بيانات المنتجات (استدعاء 2).
- بيانات الطلبات (استدعاء 3).
كل استدعاء من هذه الاستدعاءات يضيف وقتًا إضافيًا (latency) بسبب رحلة الذهاب والإياب بين العميل والسيرفر. تخيل لو أن كل استدعاء يأخذ 200 ميلي ثانية، نحن نتحدث عن أكثر من نصف ثانية ضائعة فقط في انتظار الشبكة، وهذا قبل معالجة البيانات وعرضها!
نصيحة من أبو عمر: إذا وجدت نفسك كمطور واجهة أمامية (Frontend) تقوم بعمل “chain” لعدة استدعاءات API لعرض صفحة واحدة، أو إذا كنت تستقبل كائنات ضخمة وتستخدم 10% منها فقط، فهذه علامة حمراء كبيرة على أن بنية الـ API لديك بحاجة إلى إعادة نظر.
GraphQL: المنقذ الذي أتى من فيسبوك
بعد ما “فاض الكيل”، بدأنا نبحث عن بدائل. سمعنا عن تقنية تستخدمها فيسبوك اسمها GraphQL، وبعد القراءة والبحث، أدركنا أنها قد تكون الحل الأمثل لمشاكلنا.
ما هي GraphQL بالضبط؟
GraphQL هي لغة استعلام (Query Language) لواجهات برمجة التطبيقات، بالإضافة إلى كونها بيئة تشغيل (Runtime) لتنفيذ هذه الاستعلامات باستخدام بياناتك الحالية. الفكرة الثورية فيها هي تحويل ميزان القوة من السيرفر إلى العميل.
فكر فيها بهذه الطريقة:
- REST API (مثل البوفيه المفتوح): السيرفر يقرر شكل “الطبق” (البيانات). تذهب إلى نقطة النهاية
/users/1، والسيرفر يعطيك طبقًا مليئًا بكل ما يعرفه عن المستخدم رقم 1، سواء أردت كل هذه المعلومات أم لا. - GraphQL API (مثل قائمة الطعام الانتقائية): العميل (أنت) هو من يقرر ماذا يريد في طبقه. أنت ترسل قائمة دقيقة بما تحتاجه (“أريد الاسم وصورة البروفايل للمستخدم رقم 1، واسم وسعر أول 5 منتجات جديدة”)، والسيرفر يجهز لك هذا الطبق المخصص ويرسله لك. لا زيادة ولا نقصان.
بهذه الطريقة، GraphQL تحل المشكلتين الرئيسيتين بضربة واحدة:
- لا يوجد Over-fetching: لأنك تطلب فقط ما تحتاجه.
- لا يوجد Under-fetching: لأنك تستطيع طلب كل البيانات التي تحتاجها من مصادر مختلفة (مستخدمين، منتجات، طلبات) في استدعاء واحد فقط.
من النظرية إلى التطبيق: كيف بنينا أول واجهة GraphQL
الكلام النظري جميل، لكن “الشغل” هو المحك الحقيقي. خلونا نشوف كيف طبقنا هذا بشكل عملي.
الخطوة الأولى: تعريف المخطط (Schema)
أول شيء في GraphQL هو بناء الـ “Schema”. وهو بمثابة العقد أو الدستور الذي يصف كل البيانات التي يمكن للعميل طلبها وأنواعها. إنه يوثق الـ API بشكل صارم. كتبنا الـ Schema الخاص بنا باستخدام لغة تعريف المخطط (SDL):
# يصف شكل بيانات المستخدم
type User {
id: ID!
name: String
avatarUrl: String
orders(limit: Int): [Order] # يمكن جلب طلبات المستخدم أيضًا
}
# يصف شكل بيانات المنتج
type Product {
id: ID!
name: String
price: Float
imageUrl: String
description: String
}
# يصف شكل بيانات الطلب
type Order {
id: ID!
date: String
total: Float
items: [Product]
}
# هذه هي نقطة الدخول الرئيسية للاستعلامات
type Query {
user(id: ID!): User
latestProducts(limit: Int): [Product]
}
هذا المخطط يخبر أي مطور بالضبط ما هي البيانات المتاحة وكيفية طلبها. لا مزيد من التخمين أو قراءة وثائق قديمة.
الخطوة الثانية: كتابة الاستعلام من جهة العميل
الآن، بدلًا من ثلاثة استدعاءات، أصبح تطبيق الموبايل يرسل استعلامًا واحدًا فقط إلى نقطة نهاية GraphQL الوحيدة (عادة /graphql). وهذا الاستعلام يصف بالضبط البيانات المطلوبة للواجهة الرئيسية:
query GetHomePageData {
# اطلب المستخدم الحالي
user(id: "me") {
name
avatarUrl
}
# اطلب أحدث 5 منتجات
latestProducts(limit: 5) {
name
imageUrl
price
}
# اطلب آخر طلبين للمستخدم الحالي
# لاحظ كيف يمكننا طلب بيانات مرتبطة داخل نفس الاستعلام
userOrders: user(id: "me") {
orders(limit: 2) {
id
total
}
}
}
انظروا لجمال وبساطة هذا الاستعلام. إنه يقرأ كأنه وصف للبيانات التي نريدها. طلبنا اسم المستخدم وصورته، واسم وصورة وسعر المنتجات، ورقم وتكلفة آخر طلبين، كل ذلك في طلب واحد!
الخطوة الثالثة: النتيجة السحرية (الاستجابة)
السيرفر الذي يشغل GraphQL يستقبل هذا الاستعلام، ويقوم بتجميع البيانات من المصادر المختلفة (قاعدة البيانات، خدمات أخرى)، ثم يعيد استجابة JSON شكلها يطابق تمامًا شكل الاستعلام:
{
"data": {
"user": {
"name": "أبو عمر",
"avatarUrl": "https://example.com/avatar.jpg"
},
"latestProducts": [
{
"name": "كنافة نابلسية",
"imageUrl": "https://example.com/knafeh.jpg",
"price": 10.0
},
{
"name": "زيت زيتون بكر",
"imageUrl": "https://example.com/olive-oil.jpg",
"price": 15.0
}
// ... 3 منتجات أخرى
],
"userOrders": {
"orders": [
{ "id": "order-xyz", "total": 25.0 },
{ "id": "order-pqr", "total": 50.0 }
]
}
}
}
طلب واحد، استجابة واحدة، تحتوي بالضبط على ما نحتاجه. لا بايت واحد ضائع. كانت النتيجة فورية: الواجهة الرئيسية للتطبيق أصبحت تُحمّل بسرعة البرق. شعور لا يوصف بالرضا! 🚀
GraphQL ليست الحل السحري لكل شيء
يا جماعة، من خبرتي، تعلمت أنه لا توجد تقنية هي “الحل السحري” لكل المشاكل. GraphQL قوية جدًا، لكنها ليست دائمًا الخيار الصحيح. من المهم أن نكون واقعيين ونعرف متى نستخدمها.
متى قد لا تكون GraphQL الخيار الأمثل؟
- للتطبيقات البسيطة جدًا: إذا كان لديك API صغير جدًا مع عدد قليل من نقاط النهاية الثابتة وعميل واحد فقط (مثل لوحة تحكم داخلية بسيطة)، قد تكون REST API أسرع في التنفيذ وأسهل في الإدارة.
- التخزين المؤقت (Caching): التخزين المؤقت على مستوى HTTP أسهل بكثير مع REST. يمكنك تخزين استجابة
GET /api/productsبأكملها. مع GraphQL، كل استعلام قد يكون فريدًا، مما يجعل التخزين على مستوى الشبكة أصعب. الحل يكون عادة في التخزين على مستوى العميل (باستخدام مكتبات مثل Apollo Client). - رفع الملفات: رفع الملفات ليس جزءًا أساسيًا من مواصفات GraphQL. يتطلب حلولًا إضافية (مثل استخدام multipart request)، بينما هو أمر مباشر جدًا في REST.
نصيحتي: فكر في “العميل” أولاً
القرار بين REST و GraphQL يجب أن يعتمد على احتياجات “العملاء” الذين سيستهلكون الـ API.
- هل لديك عدة عملاء مختلفين (تطبيق موبايل، موقع ويب، تطبيق ديسكتوب) باحتياجات بيانات مختلفة؟ GraphQL هو صديقك المفضل.
- هل واجهاتك الأمامية تتطور بسرعة وتحتاج إلى مرونة في طلب البيانات دون انتظار تحديثات من فريق الـ Backend؟ GraphQL ستمنحك هذه القوة.
- هل لديك API بسيط ومستقر يخدم غرضًا واحدًا محددًا؟ ربما REST كافٍ ويفي بالغرض.
الخلاصة: استرجع السيطرة على بياناتك 💡
كانت رحلتنا مع GraphQL بمثابة نقلة نوعية في طريقة تفكيرنا ببناء الواجهات البرمجية. لقد حررتنا من قيود الـ REST API التقليدية، وأعطت مطوري الواجهات الأمامية (Frontend) القوة والمرونة التي كانوا يحتاجونها لبناء تجارب مستخدم سريعة وفعالة.
الانتقال إلى GraphQL حل لنا مشاكل حقيقية تتعلق بالأداء واستهلاك الموارد، وقلل من الاعتمادية بين فريق الـ Frontend والـ Backend. لم نعد نسمع جملة “نحتاج نقطة نهاية جديدة” لكل تغيير بسيط في الواجهة.
نصيحتي الأخيرة لكم: لا تخافوا من تجربة التقنيات الجديدة التي تحل مشاكل حقيقية تواجهونها. قد تكون هناك فترة تعلم في البداية، لكن العائد على المدى الطويل يمكن أن يكون هائلاً. ابدأ بمشروع جانبي صغير، جرب بناء Schema بسيط، وشاهد بنفسك كيف يمكن لـ GraphQL أن تغير طريقة عملك للأفضل.
ويلا، شدوا حيلكم يا شباب! 💪