يا جماعة الخير، السلام عليكم ورحمة الله.
بتذكر قبل كم سنة، يمكن في 2016 أو 2017، كنّا قاعدين في المكتب بنشتغل على تطبيق موبايل لمطعم مشهور. تطبيق بسيط: يعرض قائمة الأكل، ملف المستخدم، وطلباته السابقة. وقتها، كان كل شغلنا على واجهات REST. كانت هي “الأصول” وما كان في بديل معروف بنفس القوة.
المشكلة بلشت تظهر مع أول شاشة في التطبيق، الشاشة الرئيسية. عشان نعرضها، كنا محتاجين: معلومات المستخدم اللي مسجل دخوله (اسمه وصورته)، قائمة بأجدد 3 عروض، وعدد الأطباق في سلته. حسب منطق REST، هذا يعني 3 طلبات (requests) منفصلة للسيرفر:
GET /api/users/meGET /api/promotions?limit=3GET /api/cart/count
كل طلب بروح وبرجع لحاله. على شبكة واي فاي سريعة في المكتب، الأمور “ماشي حالها”. لكن لما نجرّب التطبيق على شبكة 3G ضعيفة، كانت المصيبة. الشاشة بتضلها بيضا لوقت طويل، والعناصر بتظهر بالتقسيط: أول إشي بيظهر اسم المستخدم، بعد ثانيتين بتظهر العروض، وبعدين بيظهر رقم صغير فوق أيقونة السلة. تجربة المستخدم كانت سيئة جداً، وكنا حاسين حالنا مكتفين ومش عارفين نحل المشكلة من جذورها.
هاي القصة كانت نقطة التحول اللي خلتني أبحث عن حل، وهناك التقيت لأول مرة بـ GraphQL. في البداية كنت متشكك، لكن بعد ما فهمت الفكرة، حسيت كأنه حدا ولّع الضو في غرفة معتمة. خلوني أحكيلكم الحكاية من أولها.
ما هي واجهة REST وليش “كنّا” نحبها؟
قبل ما نهاجم REST، لازم نعطيها حقها. هندسة REST (Representational State Transfer) كانت ثورة في وقتها، وهي مبنية على مفاهيم بسيطة وواضحة موجودة في الإنترنت نفسه. الفكرة الأساسية هي أن كل شيء عبارة عن “مورد” (Resource)، ولكل مورد عنوان فريد (Endpoint).
على سبيل المثال:
- للحصول على قائمة المستخدمين:
GET /users - للحصول على المستخدم رقم 5:
GET /users/5 - لإنشاء مستخدم جديد:
POST /users - لتحديث المستخدم رقم 5:
PUT /users/5
هذا النموذج كان ممتازاً لعدة أسباب:
- بسيط ومفهوم: سهل التعلم والتطبيق.
- عديم الحالة (Stateless): كل طلب يحتوي على كل المعلومات اللازمة لتنفيذه، مما يسهل التوسع (Scaling).
- استخدام معايير الـ HTTP: يعتمد على بروتوكول HTTP المعروف، مما يجعله متوافقاً مع كل شيء تقريباً.
باختصار، REST مش “عاطلة”، بالعكس، هي بنت الأساس القوي اللي وقفنا عليه لسنوات. لكن مع تطور التطبيقات، خصوصاً تطبيقات الموبايل والـ Single Page Applications، بلشت عيوبها تظهر بوضوح.
جحيم الرحلات المتعددة: Over-fetching و Under-fetching
المشكلتان الأساسيتان اللتان واجهناهما مع REST، واللتان كانتا السبب في بطء تطبيق المطعم، هما الـ Over-fetching والـ Under-fetching.
مشكلة الـ Over-fetching: “أعطيني بس الملح، مش كل المطبخ!”
الـ Over-fetching تحدث عندما يُرجع السيرفر بيانات أكثر بكثير مما يحتاجه العميل (Client). في نموذج REST، بنية الاستجابة (Response) يحددها السيرفر بشكل كامل.
مثال عملي:
في تطبيقنا، كنا نحتاج فقط اسم المستخدم وصورته الشخصية لعرضها في أعلى الشاشة. لكن الـ endpoint المسؤول عن جلب معلومات المستخدم GET /api/users/me كان يُرجع كائن JSON ضخم:
{
"id": 123,
"username": "abu_omar_dev",
"firstName": "عمر",
"lastName": "أحمد",
"email": "abu.omar@example.com",
"profilePicture": "https://.../image.png",
"bio": "مبرمج ومطور برمجيات فلسطيني...",
"dateOfBirth": "1985-01-15",
"address": {
"street": "شارع فلسطين",
"city": "رام الله",
"country": "فلسطين"
},
"lastLogin": "2023-10-26T10:00:00Z",
"createdAt": "2015-03-10T12:00:00Z",
"permissions": ["admin", "editor"],
// ... والمزيد من الحقول التي لا تلزمنا
}
تطبيق الموبايل كان يحتاج فقط حقلين (firstName و profilePicture)، لكنه كان مجبراً على تحميل واستقبال كل هذه البيانات، مما يؤدي إلى:
- هدر في استهلاك باقة الإنترنت: مشكلة كبيرة للمستخدمين على الشبكات الخلوية.
- بطء في التطبيق: يحتاج العميل وقتاً أطول لتحميل البيانات وتحليلها (Parsing).
- زيادة الحمل على السيرفر: السيرفر يعمل على جلب بيانات من قاعدة البيانات وتهيئتها دون أي داعٍ.
مشكلة الـ Under-fetching: “مشوار لكل شغلة صغيرة”
هذه المشكلة هي الوجه الآخر للعملة، وهي تماماً ما واجهناه في الشاشة الرئيسية. الـ Under-fetching تحدث عندما لا يوفر الـ endpoint الواحد كل البيانات المطلوبة، مما يجبر العميل على القيام برحلات (طلبات) إضافية للحصول على بقية المعلومات.
مثال عملي (مشكلة N+1):
لنفترض أننا نريد عرض قائمة مقالات مع اسم كاتب كل مقال. في REST، قد يبدو الأمر هكذا:
الرحلة الأولى: جلب كل المقالات.
GET /api/posts
الاستجابة ستكون مصفوفة من المقالات، كل مقال يحتوي على authorId:
[
{ "id": 1, "title": "مقدمة في GraphQL", "authorId": 10 },
{ "id": 2, "title": "أداء الواجهات البرمجية", "authorId": 15 },
{ "id": 3, "title": "REST مقابل GraphQL", "authorId": 10 }
]
لاحظ أننا حصلنا على “رقم” الكاتب، وليس اسمه. الآن، لعرض اسم الكاتب بجانب كل مقال، نحن مضطرون للقيام برحلات إضافية، رحلة لكل كاتب فريد (أو رحلة لكل مقال في أسوأ الحالات).
الرحلات الإضافية:
GET /api/users/10 // لجلب معلومات كاتب المقال الأول والثالث
GET /api/users/15 // لجلب معلومات كاتب المقال الثاني
إذا كان لدينا 20 مقالاً كتبها 20 كاتباً مختلفاً، فهذا يعني 1 (لجلب المقالات) + 20 (لجلب الكتّاب) = 21 رحلة! هذا هو ما يسمى بمشكلة “N+1 Query”، وهو كابوس حقيقي للأداء.
ظهور المنقذ GraphQL: اطلب ما تحتاجه بالضبط
هنا يأتي دور GraphQL. GraphQL ليست بديلاً كاملاً لـ REST، بل هي لغة استعلام (Query Language) للواجهات البرمجية، وفلسفتها مختلفة تماماً. الفكرة بسيطة وعبقرية: بدلاً من أن يكون للسيرفر عدة نقاط نهاية (endpoints) ثابتة، يوجد لديه نقطة نهاية واحدة ذكية تفهم الطلبات المعقدة.
فكر فيها كأنك ذهبت من بوفيه مفتوح (REST) حيث تضطر لأخذ كل ما في الصحن، إلى مطعم فاخر (GraphQL) حيث تعطي النادل قائمة دقيقة بما تريده، ويحضره لك بالضبط كما طلبت، في طبق واحد.
مع GraphQL، العميل هو من يقرر شكل البيانات التي يريدها. يرسل “استعلاماً” يصف البيانات المطلوبة بدقة، والسيرفر يرد باستجابة JSON تطابق تماماً شكل الاستعلام.
كيف يعمل GraphQL؟ (مع أمثلة كود)
لنعد إلى أمثلتنا السابقة ونرى كيف يحلها GraphQL بكل أناقة.
حل مشكلة الـ Over-fetching
بدلاً من طلب GET /api/users/me، سنرسل طلب POST إلى نقطة النهاية الوحيدة لـ GraphQL (عادة /graphql) مع الاستعلام التالي:
query GetMyProfileInfo {
me {
firstName
profilePicture
}
}
والسيرفر سيرد بـ:
{
"data": {
"me": {
"firstName": "عمر",
"profilePicture": "https://.../image.png"
}
}
}
لاحظ؟ حصلنا على ما نحتاجه بالضبط، لا أكثر ولا أقل. لا هدر في البيانات، وسرعة فائقة. طلبنا الملح، فأعطانا الملح فقط. ✨
حل مشكلة الـ Under-fetching (N+1)
الآن، لنحل مشكلة المقالات والكتّاب. بدلاً من 21 رحلة، سنقوم برحلة واحدة فقط باستعلام يطلب كل شيء نريده دفعة واحدة:
query GetPostsWithAuthors {
posts {
id
title
author {
name
}
}
}
والاستجابة السحرية ستكون:
{
"data": {
"posts": [
{
"id": 1,
"title": "مقدمة في GraphQL",
"author": {
"name": "أبو عمر"
}
},
{
"id": 2,
"title": "أداء الواجهات البرمجية",
"author": {
"name": "علي"
}
},
{
"id": 3,
"title": "REST مقابل GraphQL",
"author": {
"name": "أبو عمر"
}
}
]
}
}
في رحلة واحدة، حصلنا على قائمة المقالات ومعها اسم كاتب كل مقال. لقد قضينا على مشكلة N+1 من جذورها.
المخطط (Schema) والأنواع (Types): العقد اللي بينك وبين السيرفر
قد تتساءل: “كيف يعرف السيرفر كيفية ربط المقالات بالكتّاب؟”. الجواب يكمن في “المخطط” أو الـ Schema. المخطط هو العقد المكتوب بين العميل والسيرفر. هو يصف كل البيانات المتاحة في الواجهة البرمجية وكيفية الوصول إليها.
مثال بسيط على مخطط لمثالنا السابق باستخدام لغة تعريف المخطط (SDL):
# يصف أنواع البيانات المتاحة
type Post {
id: ID!
title: String!
content: String
author: User! # كل مقال له كاتب من نوع User
}
type User {
id: ID!
name: String!
email: String
posts: [Post!] # كل مستخدم يمكن أن يكون لديه قائمة من المقالات
}
# يصف الاستعلامات الممكنة
type Query {
posts: [Post!]
post(id: ID!): Post
users: [User!]
user(id: ID!): User
me: User
}
هذا المخطط هو مصدر الحقيقة (Single Source of Truth). يخبر مطوري الواجهة الأمامية (Frontend) بالبيانات التي يمكنهم طلبها، ويساعد أدوات المطورين على توفير ميزات رائعة مثل الإكمال التلقائي والتحقق من صحة الاستعلامات قبل إرسالها.
نصائح من خبرة أبو عمر: متى تستخدم GraphQL؟
بعد كل هذا المديح، قد تظن أن GraphQL هو الحل لكل المشاكل. الحقيقة، “كل شي بزيد عن حده بنقلب ضده”. GraphQL أداة قوية جداً، لكنها ليست دائماً الأداة المناسبة. الشاطر هو اللي بيعرف متى يستخدم المطرقة ومتى يستخدم المفك.
استخدم GraphQL في الحالات التالية:
- تطبيقات الموبايل: حيث تكون الشبكة غير مستقرة واستهلاك البيانات حساساً.
- تطبيقات الصفحة الواحدة (SPAs): مثل React و Vue و Angular، التي تحتوي على واجهات مستخدم معقدة تحتاج بيانات من مصادر متعددة.
- عندما يكون لديك عدة عملاء: مثل تطبيق ويب، تطبيق موبايل (iOS/Android)، وساعة ذكية. GraphQL يسمح لكل عميل بطلب البيانات التي يحتاجها فقط.
- عندما تريد تمكين فرق الواجهة الأمامية: GraphQL يمنح مطوري الـ Frontend استقلالية أكبر لتجربة وتطوير الميزات دون الحاجة لطلب تغييرات مستمرة من فريق الـ Backend.
قد يكون REST خياراً أفضل في الحالات التالية:
- الواجهات البرمجية البسيطة جداً: إذا كان لديك تطبيق CRUD بسيط، فإن تعقيد إعداد GraphQL قد لا يكون مبرراً.
- الواجهات التي تركز على الملفات والموارد الثابتة: REST ممتازة في التعامل مع الموارد مثل الصور والفيديوهات، والاستفادة من آليات التخزين المؤقت (Caching) على مستوى الـ HTTP.
- عندما يكون فريقك غير مستعد: يتطلب GraphQL منحنى تعلم. إذا كان فريقك مرتاحاً جداً مع REST والوقت ضيق، قد لا يكون الانتقال هو الخيار الأمثل الآن.
الخلاصة: اختر الأداة المناسبة للمهمة
في النهاية، قصة GraphQL و REST ليست قصة صراع بين الخير والشر. هي قصة تطور. REST وضعت الأساس، و GraphQL جاء ليبني عليه ويحل مشاكل ظهرت مع تطور تطبيقاتنا.
بالنسبة لنا في مشروع تطبيق المطعم، كان الانتقال إلى GraphQL هو طوق النجاة. قمنا ببناء واجهة GraphQL فوق واجهات REST القديمة كخطوة أولى، والنتيجة كانت مذهلة. أداء التطبيق تحسن بشكل جذري، وتجربة المستخدم أصبحت سلسة، والأهم من ذلك، أن معنويات فريق التطوير ارتفعت لأننا أصبحنا قادرين على بناء الميزات بسرعة وكفاءة.
نصيحتي الأخيرة لك: لا تخف من التجربة. إذا كنت تعاني من نفس المشاكل التي وصفناها، ابدأ صغيراً. جرّب بناء واجهة GraphQL بسيطة لمشروع جانبي، أو حتى قم بإنشاء “طبقة” GraphQL فوق واجهة REST موجودة لديك باستخدام أدوات مثل Apollo Server.
شاهد الفرق بنفسك، وبعدها قرر. يلا يا جماعة، شدّوا الهمة، عالم البرمجة واسع ويحتاج منا التعلم المستمر. بالتوفيق! 💪