يا أهلاً وسهلاً فيكم جميعاً، معكم أخوكم أبو عمر.
قبل كم سنة، كنا شغالين على مشروع كبير، منصة تواصل اجتماعي جديدة. الفريق كان مليان حماس والطموحات عالية، وأنا كنت مسؤول عن فريق الواجهات الخلفية (Backend). الأمور كانت ماشية تمام بالبداية، وبنينا واجهاتنا البرمجية (APIs) باستخدام معمارية REST المعتادة، واللي كلنا بنعرفها وبنحبها… أو هيك كنا مفكرين.
بعد أشهر، ومع نمو المشروع، بدأت المشاكل تظهر زي المطر. فريق تطبيق الموبايل بيجي يحكيلي: “يا أبو عمر، صفحة عرض قائمة المستخدمين بطيئة جداً، ليش بترجعولنا كل بيانات المستخدم؟ إحنا بس بدنا الاسم والصورة الشخصية!”. بنفس اليوم، بيجي فريق الويب بيحكي: “يا أبو عمر، عشان نعرض صفحة بروفايل المستخدم مع آخر 3 منشورات إله، لازم نبعت طلبين للـ API، واحد يجيب بيانات المستخدم والتاني يجيب منشوراته، هاد الأشي عم يعمل تأخير!”.
الوضع صار “شوربة” بمعنى الكلمة. فريق الواجهة الخلفية صار غرقان في طلبات إنشاء نقاط نهاية (endpoints) جديدة ومخصصة: /api/v1/users/light عشان الموبايل، و/api/v2/users/with-posts عشان الويب. كل تغيير بسيط في الواجهة الأمامية كان يتطلب شغل وتعديل في الواجهة الخلفية. حسينا حالنا صرنا زي موظف استقبال في فندق فوضوي، كل زبون بده طلب شكل، وإحنا مش ملحقين. كنا في جحيم حقيقي اسمه الجلب المفرط والناقص للبيانات.
في ليلة من هالليالي، وأنا ببحث عن حل، قرأت عن تقنية اسمها GraphQL. بالبداية كنت متشكك، بس كل ما قرأت أكتر، كل ما حسيت إنه ممكن يكون هاد هو طوق النجاة. ومن هنا بدأت رحلتنا اللي بدي أحكيلكم عنها اليوم.
ما هو الجحيم الذي كنا نعيشه؟ (مشاكل REST API التقليدية)
قبل ما نحكي عن الحل، خلونا نفصّل أكتر بالمشاكل اللي واجهتنا مع REST، واللي أنا متأكد إنه كتير منكم بعاني منها.
مشكلة الجلب المفرط (Over-fetching)
باختصار، الجلب المفرط يعني إنك تطلب بيانات من الـ API، فترجعلك بيانات أكتر بكتير من اللي بتحتاجها. في مثالنا السابق، لما تطبيق الموبايل كان يحتاج فقط اسم المستخدم وصورته، كانت نقطة النهاية GET /api/users/{id} ترجع كائن JSON ضخم:
{
"id": 123,
"username": "abu_omar_dev",
"firstName": "عمر",
"lastName": "أحمد",
"email": "abu.omar@example.com",
"avatarUrl": "https://example.com/avatar.jpg",
"bio": "مبرمج فلسطيني بحب القهوة والكود النظيف...",
"address": {
"street": "شارع يافا",
"city": "القدس",
"country": "فلسطين"
},
"createdAt": "2023-10-26T10:00:00Z",
"lastLogin": "2023-10-26T12:30:00Z",
"orderHistory": [ ... ] // قائمة طويلة من الطلبات السابقة
}
تطبيق الموبايل كان يحتاج فقط حقلي username و avatarUrl. كل البيانات الباقية هي عبء على الشبكة، بتستهلك من باقة الإنترنت للمستخدم، وبتبطئ تحميل التطبيق. هذا هو الجلب المفرط بعينه.
مشكلة الجلب الناقص (Under-fetching) وتعدد الطلبات
هذه هي المشكلة المعاكسة تماماً. الجلب الناقص يعني أن نقطة النهاية لا توفر كل البيانات التي تحتاجها، مما يضطرك لإرسال طلبات إضافية. في مثال فريق الويب، لعرض صفحة المستخدم ومنشوراته، كانوا بحاجة لـ:
- إرسال طلب
GET /api/users/{id}للحصول على معلومات المستخدم. - بعد وصول الرد، إرسال طلب آخر
GET /api/users/{id}/postsللحصول على منشوراته.
هذا يعني رحلتين ذهاب وإياب (round trips) بين العميل والخادم. كل رحلة تأخذ وقتاً، والمجموع يؤدي إلى تجربة مستخدم بطيئة ومحبطة، خصوصاً على الشبكات الضعيفة.
صداع إدارة الإصدارات ونقاط النهاية
مع تزايد متطلبات الواجهات الأمامية المختلفة (ويب، موبايل، ويمكن مستقبلاً ساعة ذكية)، وجدنا أنفسنا نغرق في إصدارات مختلفة من الـ API (v1, v2) ونقاط نهاية مخصصة لا تنتهي. هذا جعل الصيانة كابوساً، وأي تعديل على بنية البيانات كان يتطلب تحديث أماكن كثيرة، مما يزيد من احتمالية الأخطاء.
المنقذ GraphQL: كيف يعمل السحر؟
بعد ما شخصنا المرض، صار وقت نحكي عن الدوا. GraphQL هي لغة استعلام (Query Language) للـ API، وبنفس الوقت هي بيئة تشغيل لتنفيذ هذه الاستعلامات باستخدام بياناتك الحالية. خلوني أبسطها: GraphQL بتسمح للعميل (الواجهة الأمامية) إنه يطلب بالضبط البيانات اللي بده ياها، لا زيادة ولا نقصان، وكل هاد بطلب واحد فقط.
نصيحة أبو عمر: كتير ناس بتفكر GraphQL قاعدة بيانات جديدة أو إطار عمل خاص. لأ يا جماعة! GraphQL هي مجرد طبقة (layer) بتحطها فوق الواجهة الخلفية تبعتك (سواء كانت REST APIs, Microservices, أو اتصال مباشر بقاعدة البيانات). هي بتنظم طريقة الحكي بين العميل والخادم.
المفاهيم الأساسية: Schema, Queries, Mutations
قوة GraphQL تكمن في ثلاثة مفاهيم رئيسية:
1. المخطط (Schema) والأنواع (Types)
الـ Schema هو العقد بين الواجهة الأمامية والخلفية. هو ملف نصي بتوصف فيه كل البيانات اللي ممكن العميل يطلبها وأنواعها والعلاقات بينها. هذا المخطط هو مصدر الحقيقة الأوحد (Single Source of Truth).
مثلاً، هيك بنعرّف أنواع المستخدم والمنشور في مشروعنا:
# وصف لنوع المستخدم
type User {
id: ID!
username: String!
avatarUrl: String
posts: [Post!]
}
# وصف لنوع المنشور
type Post {
id: ID!
title: String!
content: String
author: User!
}
# نقطة الدخول الرئيسية للاستعلامات
type Query {
user(id: ID!): User
posts: [Post!]
}
هذا المخطط واضح جداً: لدينا `User` و `Post`. المستخدم لديه قائمة من المنشورات، والمنشور له مؤلف من نوع `User`. والـ `Query` تعرّف الاستعلامات الممكنة.
2. الاستعلامات (Queries)
هنا يكمن سحر GraphQL. العميل هو من يحدد شكل البيانات اللي بده ياها. بدل ما يطلب نقطة نهاية ثابتة، بيبعت استعلام بيشبه JSON في شكله.
لحل مشكلة فريق الموبايل (الجلب المفرط)، صاروا يبعتوا هاد الاستعلام:
query GetUserForMobile {
user(id: "123") {
username
avatarUrl
}
}
والرد بيكون بالضبط على قد الطلب، لا أقل ولا أكثر:
{
"data": {
"user": {
"username": "abu_omar_dev",
"avatarUrl": "https://example.com/avatar.jpg"
}
}
}
ولحل مشكلة فريق الويب (الجلب الناقص)، صاروا يبعتوا هاد الاستعلام في طلب واحد:
query GetUserWithPostsForWeb {
user(id: "123") {
username
bio
posts {
id
title
}
}
}
والرد بيجيهم كامل متكامل في استجابة واحدة، مما قضى على مشكلة الطلبات المتعددة.
3. التعديلات (Mutations)
إذا كانت الـ Queries للقراءة (Read)، فالـ Mutations هي للكتابة والتعديل (Create, Update, Delete). طريقة كتابتها تشبه الـ Queries، لكنها تستخدم كلمة `mutation`.
مثلاً، لإنشاء منشور جديد:
mutation CreateNewPost {
createPost(title: "مقالتي عن GraphQL", content: "...") {
id
title
}
}
الجميل هنا أنك تستطيع أيضاً أن تطلب بيانات من الكائن الذي تم إنشاؤه في نفس الرد.
من النظرية إلى التطبيق: رحلة تحولنا إلى GraphQL
الكلام النظري حلو، بس كيف طبقنا هاد الأشي فعلياً؟
الخطوة الأولى: بناء المخطط (Schema)
أول وأهم خطوة كانت جلسة عصف ذهني بين كل الفرق (خلفية، أمامية، موبايل) لتصميم المخطط. هذه الخطوة حاسمة لأن المخطط هو لغة التخاطب الجديدة بيننا.
نصيحة أبو عمر: لا تستعجلوا في هذه المرحلة. المخطط الجيد هو أساس كل شيء. ابدأوا بتعريف الأنواع الأساسية والعلاقات بينها قبل كتابة أي سطر كود للمعالجات (Resolvers). المخطط هو وعدكم للواجهة الأمامية، فاجعلوه وعداً جيداً.
الخطوة الثانية: كتابة المُعالِجات (Resolvers)
الـ Resolver هو عبارة عن دالّة (function) وظيفتها جلب البيانات لحقل معين في المخطط. لكل حقل في الـ Schema، هناك Resolver مقابل له.
مثلاً، باستخدام Apollo Server (مكتبة مشهورة لـ GraphQL في بيئة Node.js)، هكذا يبدو شكل الـ Resolvers:
const resolvers = {
Query: {
// Resolver للحقل user في الـ Query
user: (parent, args, context, info) => {
// args.id يحتوي على الـ ID المطلوب
// هنا نكتب كود جلب المستخدم من قاعدة البيانات
return db.users.findById(args.id);
},
},
User: {
// Resolver للحقل posts داخل نوع User
posts: (user, args, context, info) => {
// user يحتوي على بيانات المستخدم الذي تم جلبه في الـ Resolver الأب
// هنا نكتب كود جلب منشورات هذا المستخدم
return db.posts.findByAuthorId(user.id);
},
},
};
لاحظ أن GraphQL لا يغير منطق عملك في جلب البيانات، بل ينظمه. أنت ما زلت تكتب نفس استعلامات قاعدة البيانات، لكن الآن كل استعلام مربوط بحقل معين في المخطط.
نصائح من قلب الميدان (من خبرة أبو عمر)
الانتقال لـ GraphQL ما كان كله وردي، تعلمنا دروس قاسية بالطريق، وبدي أشاركم بعضها عشان تتجنبوها:
- الأمان والتصاريح (Authorization): في REST، كنا نضع middleware على نقطة النهاية للتأكد من صلاحيات المستخدم. في GraphQL، لديك نقطة نهاية واحدة (عادة
/graphql). لذا، يجب وضع منطق التحقق من الصلاحيات داخل كل Resolver على حدة، أو في ما يسمى بـ `context` object الذي يتم تمريره لكل Resolvers. لا تهملوا هذه النقطة. - مشكلة N+1: هذه مشكلة أداء شائعة. تخيل أنك طلبت قائمة من 10 مستخدمين، ولكل مستخدم منشوراته. بدون تحسين، سيقوم الـ Resolver بتنفيذ استعلام لجلب 10 مستخدمين، ثم 10 استعلامات أخرى (واحد لكل مستخدم) لجلب منشوراتهم، أي N+1 استعلامات (11 في حالتنا). الحل يكمن في استخدام تقنية اسمها Batching and Caching باستخدام مكتبة مثل DataLoader التي تجمع كل طلبات جلب المنشورات في استعلام واحد.
- التعامل مع الأخطاء: GraphQL دائماً ما يرجع استجابة بحالة
200 OKحتى لو فشل جزء من الاستعلام. الأخطاء تكون موجودة داخل مصفوفة `errors` في جسم الرد. يجب على الواجهة الأمامية دائماً التحقق من وجود هذه المصفوفة. - التخزين المؤقت (Caching): التخزين المؤقت على مستوى الشبكة (HTTP Caching) أصعب مع GraphQL لأن كل الطلبات تذهب لنفس الـ URL. الحل يكمن في التخزين المؤقت على مستوى العميل باستخدام مكتبات مثل Apollo Client أو Relay التي تقوم بعمل Normalization للبيانات وتخزينها بذكاء.
الخلاصة: هل GraphQL هو الحل لكل المشاكل؟ 💡
من الآخر، GraphQL كان نقلة نوعية لفريقنا. أعطانا المرونة التي كنا نحلم بها، سرّع من تطوير الواجهات الأمامية بشكل كبير، وقلل من الاعتمادية على الفريق الخلفي لكل تغيير صغير.
لكن، هل هو حل سحري لكل المشاريع؟ طبعاً لا. GraphQL يأتي مع منحنى تعلم وتعقيد إضافي في الإعداد الأولي. لمشروع بسيط جداً أو API داخلية بين خدمتين، قد تكون REST API التقليدية أبسط وأسرع في التنفيذ.
نصيحتي الأخيرة لكم: إذا كان مشروعكم يحتوي على عملاء متعددين (ويب، موبايل،…) بمتطلبات بيانات مختلفة ومتغيرة، أو إذا كنتم تعانون من “شوربة” نقاط النهاية والجلب المفرط والناقص، فأنصحكم بشدة أن تعطوا GraphQL فرصة حقيقية. ابدأوا بمشروع جانبي صغير، جربوا بأنفسكم، وشوفوا الفرق.
يلا يا جماعة، شدّوا حيلكم، عالم البرمجة واسع وجميل ومليء بالحلول المبتكرة. 🚀