يا جماعة الخير، يسعد مساكم. خليني أحكيلكم قصة صارت معي قبل كم سنة، قصة بتلخص معاناة كثير مبرمجين مع الواجهات البرمجية (APIs). كنا شغالين على تطبيق موبايل لشركة ناشئة، تطبيق فيه تفاصيل اجتماعية كثيرة: بروفايلات مستخدمين، منشورات، تعليقات، وهالحكي هاد. وفي يوم من الأيام، إجاني مدير المشروع، وجهه أصفر، وقلي: “يا أبو عمر، الحقني! المستخدمين بشتكوا التطبيق بطيء زي السلحفاة، والزبون نفسه جرب التطبيق وحكالي إنه “بفتح النفس على الأكل، من كثر ما الواحد بستنى ليحمّل!”.
شعرت بالدم يغلي في عروقي، إحنا فريق شاطر والتطبيق مكتوب بكود نظيف، شو القصة؟ نزلت على مكتبي، فتحت أدوات المطورين في المتصفح وشغلت التطبيق على المحاكي، وبديت أراقب الشبكة (Network Tab). وهون كانت الصدمة… شفت طلب API واحد لصفحة بروفايل المستخدم، حجم البيانات اللي راجعة كان بالميغابايت! الصفحة نفسها ما بتحتاج إلا اسم المستخدم وصورته وكم معلومة بسيطة، لكن الـ API كان يرجع كل تاريخ حياة المستخدم: كل منشوراته، كل تعليقاته، كل أصدقائه، حتى سجل طلباته من مطعم شاورما قريب!
وقتها ضحكت وقلت لزميلي اللي جنبي: “يا زلمة، إحنا بنبعت شاحنة نقل كبيرة عشان نوصل رسالة صغيرة مكتوبة على ورقة!”. كانت هذه هي اللحظة التي أدركت فيها أننا غارقون في جحيم الـ Over-fetching، وأن الوقت قد حان للتغيير الجذري.
ما هو الـ Over-fetching؟ أو “مشكلة الشاحنة”
ببساطة شديدة، الـ Over-fetching (أو الإفراط في جلب البيانات) يعني أن الواجهة البرمجية (API) تُرجع بيانات أكثر بكثير مما يحتاجه العميل (Client) فعليًا. تخيل أنك تريد فقط معرفة عنوان كتاب من المكتبة، فتذهب لأمين المكتبة ويسلمك موسوعة كاملة عن تاريخ صناعة الورق في الصين. هذا بالضبط ما تفعله واجهات REST التقليدية أحيانًا.
في عالم REST API، من الشائع أن يكون لديك نقطة نهاية (Endpoint) مثل /api/users/:id. هذه النقطة مصممة لتعيد كل معلومات المستخدم. دعونا نرى مثالاً:
مثال على طلب REST تقليدي
لنفترض أن واجهة المستخدم (UI) تحتاج فقط لعرض اسم المستخدم وصورته الرمزية في رأس الصفحة. يقوم العميل بإرسال طلب GET إلى:
GET /api/users/123
لكن الخادم (Server)، الذي لا يعرف ماذا تحتاج الواجهة بالضبط، يرسل استجابة كاملة قد تبدو هكذا:
{
"id": 123,
"username": "abu_omar_dev",
"email": "abu.omar@example.com",
"fullName": "أبو عمر الفلسطيني",
"avatarUrl": "https://example.com/avatars/123.jpg",
"bio": "مبرمج ومطور برمجيات فلسطيني خبير في الذكاء الاصطناعي والتقنيات الحديثة...",
"createdAt": "2020-01-15T10:00:00Z",
"posts": [
{ "id": 1, "title": "مقالتي الأولى", "content": "..." },
{ "id": 2, "title": "مقالتي الثانية", "content": "..." }
// ... وقد يكون هناك 500 منشور آخر
],
"followers": [
{ "id": 124, "username": "user1" },
{ "id": 125, "username": "user2" }
// ... وقد يكون هناك 10000 متابع آخر
]
}
الواجهة الأمامية كانت تحتاج فقط إلى
fullNameوavatarUrl، لكنها استلمت “شاحنة” مليئة بالمنشورات والمتابعين والبيانات غير الضرورية في تلك اللحظة. هذا يسبب بطء في الشبكة، استهلاك أعلى للبيانات (خاصة على الموبايل)، وزيادة في استهلاك الذاكرة في المتصفح أو التطبيق. اشي بيرفع الضغط!
محاولاتنا “الترقيعية” لحل المشكلة قبل GraphQL
طبعاً لم نقف مكتوفي الأيدي. حاولنا حل المشكلة بأساليب مختلفة باستخدام REST:
- نقاط نهاية متعددة (Multiple Endpoints): قمنا بإنشاء نقاط نهاية مخصصة. مثلاً:
/api/users/123/basicللحصول على المعلومات الأساسية، و/api/users/123/fullللحصول على كل شيء. لكن هذا تحول إلى كابوس صيانة مع نمو التطبيق. أصبح لدينا عشرات النقاط لكل موديل (model). - معلمات الاستعلام (Query Parameters): استخدمنا طريقة مثل
/api/users/123?fields=fullName,avatarUrl. هذا كان أفضل، لكنه لم يكن معيارياً، وكان يتطلب كتابة منطق معقد في الواجهة الخلفية لتحليل هذه المعلمات، وسرعان ما يصبح فوضوياً.
كانت هذه الحلول مجرد “ترقيع”. كنا بحاجة إلى حل جذري يغير طريقة تفكيرنا في تصميم الـ APIs.
ودخل البطل: GraphQL!
وهنا يأتي دور GraphQL. عندما سمعت عنه لأول مرة، ظننت أنه مجرد مكتبة أو إطار عمل جديد. لكنه في الحقيقة أعمق من ذلك بكثير. GraphQL هي لغة استعلام للـ APIs، وفلسفتها بسيطة وعبقرية: العميل يقرر ما يريد، والخادم يلتزم بتنفيذ طلبه بالضبط. لا زيادة ولا نقصان.
بدلاً من وجود عشرات نقاط النهاية، مع GraphQL يكون لديك عادةً نقطة نهاية واحدة فقط (مثلاً /graphql) ترسل إليها كل استعلاماتك.
GraphQL على أرض الواقع: لنحل مشكلة البروفايل
باستخدام GraphQL، العملية تختلف تمامًا. أولاً، في الواجهة الخلفية، نقوم بتعريف “مخطط” (Schema) يصف كل البيانات التي يمكن للعميل طلبها وأنواعها.
1. تعريف المخطط (Schema)
يبدو المخطط الخاص بالمستخدم شيئًا كهذا (باستخدام GraphQL’s Schema Definition Language):
type User {
id: ID!
username: String!
fullName: String
avatarUrl: String
posts: [Post!]
}
type Post {
id: ID!
title: String!
content: String
}
type Query {
user(id: ID!): User
}
هذا المخطط هو بمثابة “عقد” بين الواجهة الأمامية والخلفية. إنه يوثق نفسه بنفسه!
2. الاستعلام من العميل (The Query)
الآن، عندما تحتاج الواجهة الأمامية إلى اسم المستخدم وصورته فقط، فإنها ترسل استعلامًا (Query) يشبه JSON ولكن بدون قيم. يبدو هكذا:
query GetUserHeader {
user(id: "123") {
fullName
avatarUrl
}
}
3. الاستجابة من الخادم (The Response)
الخادم الذي يفهم GraphQL يستقبل هذا الاستعلام، ويرى بالضبط ما هي الحقول المطلوبة، ويُرجع استجابة JSON تتطابق مع بنية الاستعلام 100%:
{
"data": {
"user": {
"fullName": "أبو عمر الفلسطيني",
"avatarUrl": "https://example.com/avatars/123.jpg"
}
}
}
لاحظ الجمال هنا! لا بيانات إضافية، لا منشورات، لا متابعين. فقط ما طلبناه. لقد أرسلنا “دراجة هوائية” لتوصيل رسالتنا الصغيرة، وهذا بالضبط ما كنا نحتاجه. 🚲
نصائح من مطبخ أبو عمر (خبرة عملية)
بعد العمل على عدة مشاريع باستخدام GraphQL، جمعت لكم بعض النصائح العملية:
- لا تستبدل كل شيء دفعة واحدة: لست مضطرًا لإعادة كتابة كل واجهاتك البرمجية. يمكنك البدء بوضع واجهة GraphQL “فوق” واجهات REST الحالية. ابدأ بميزة جديدة أو بجزء من التطبيق يعاني بشدة من الـ Over-fetching.
- فكر في التخزين المؤقت (Caching): واجهات REST سهلة التخزين على مستوى HTTP (باستخدام GET). مع GraphQL، الأمر يتطلب استراتيجية مختلفة لأن كل الطلبات تذهب لنقطة نهاية واحدة (عادةً POST). مكتبات مثل Apollo Client و Relay لديها حلول تخزين مؤقت ذكية جدًا على مستوى العميل.
- احذر من مشكلة N+1: هذه مشكلة شائعة في GraphQL حيث يمكن أن يؤدي استعلام واحد من العميل إلى تنفيذ N+1 استعلام في قاعدة البيانات. لحسن الحظ، هناك حلول رائعة مثل مكتبة DataLoader التي تقوم بتجميع الاستعلامات (Batching) وحل هذه المشكلة بكفاءة.
- استثمر في الأدوات: النظام البيئي لـ GraphQL مليء بالأدوات الرائعة. أدوات مثل GraphiQL أو Apollo Studio تجعل استكشاف وتجربة الـ API متعة حقيقية، فهي توفر لك وثائق حية وإكمال تلقائي لاستعلاماتك.
الخلاصة: هل نرسل شاحنة أم دراجة هوائية؟ 😉
الانتقال إلى GraphQL كان نقلة نوعية في مشاريعنا. لقد أعطى القوة للمطورين في الواجهة الأمامية لطلب ما يحتاجونه بالضبط، مما أدى إلى تطبيقات أسرع، تجربة مستخدم أفضل، وتوفير كبير في استهلاك البيانات. لم نعد نرسل “شاحنات” بشكل افتراضي، بل أصبحنا نختار وسيلة النقل المناسبة لكل مهمة.
GraphQL ليس حلاً سحرياً لكل المشاكل، وواجهات REST ما زالت قوية ومناسبة جدًا في كثير من الحالات. لكن عندما تواجه جحيم الـ Over-fetching و الـ Under-fetching (الحاجة لعمل عدة طلبات للحصول على البيانات اللازمة)، فإن GraphQL يقدم حلاً أنيقًا وقويًا يستحق التجربة.
نصيحتي لك: ابدأ بمشروع جانبي صغير، قم ببناء خادم GraphQL بسيط، وجرب بنفسك. شاهد كيف يمكنك تشكيل البيانات التي تريدها بالضبط. أنا متأكد أنك ستعجب بالمرونة والقوة التي يمنحك إياها. بالتوفيق يا جماعة! 👍