حكاية “صفحة المستخدم” التي كادت أن تُفشّل المشروع
يا جماعة الخير، السلام عليكم. معكم أخوكم أبو عمر.
قبل كم سنة، كنت شغال مع فريق على تطبيق اجتماعي جديد، تطبيق طموح وفيه ميزات كثيرة. وصلنا لمرحلة تطوير “صفحة المستخدم” (User Profile). للوهلة الأولى، المهمة تبدو بسيطة: عرض صورة المستخدم، اسمه، نبذة عنه، وآخر 5 منشورات كتبها. لكن هون بلشت المشاكل اللي “بتطلّع الشيب براس الواحد”.
فريق الواجهة الأمامية (Frontend) كان يصرخ: “يا أبو عمر، بدنا نعرض اسم المستخدم وصورته بس في الهيدر، ليش بنضطر نحمّل كل بياناته الشخصية من عنوانه لتاريخ ميلاده في طلب واحد؟ التطبيق بطيء على الإنترنت الضعيف!”. كانوا على حق، هذه المشكلة اسمها Over-fetching، أو “الجلب الزائد للبيانات”. كنا نغرقهم ببيانات لا يحتاجونها.
وبنفس الوقت، عشان يعرضوا آخر 5 منشورات، كانوا يبعثوا طلب يجيب بيانات المستخدم، وبعدها طلب ثاني يجيب قائمة ID المنشورات، وبعدها 5 طلبات منفصلة، طلب لكل منشور عشان يجيبوا تفاصيله! تخيلوا الكارثة؟ 7 طلبات شبكة (Network Requests) عشان نعرض صفحة واحدة! هذه المشكلة اسمها Under-fetching، أو “الجلب الناقص للبيانات”، اللي بتجبرك تعمل رحلات مكوكية للسيرفر. التطبيق كان يستجدي البيانات قطعة قطعة.
الوضع كان متأزم، اجتماعات طويلة، واللوم يترمى بين فريق الواجهة الخلفية (Backend) والواجهة الأمامية. إحنا كـ Backend نقول “اعملوا طلب واحد وخذوا اللي بدكم ياه”، وهم يقولوا “الطلب الواحد فيه جيجات من الداتا ما بدنا إياها!”. شعرنا أننا في جحيم تقني، إلى أن قررنا تجربة حل كان الكل يتكلم عنه وقتها: GraphQL.
ما هو جحيم الـ Over-fetching والـ Under-fetching؟
قبل ما نغطس في الحل، خلينا نفصّل المشكلة أكثر عشان تكون واضحة للجميع، خصوصًا للمبرمجين الجدد.
مشكلة الـ Over-fetching: عندما تغرق في البيانات
تخيل أنك ذهبت لمطعم لتطلب كوب ماء، فقام النادل بإحضار بوفيه كامل يتضمن كل ما في المطبخ، ووضع لك الفاتورة كاملة. هذا بالضبط هو الـ Over-fetching في عالم الـ APIs.
باستخدام REST API التقليدي، غالبًا ما يكون لديك نقاط وصول (Endpoints) ثابتة مثل /api/users/123. هذه النقطة قد تُرجع كائن JSON ضخمًا:
{
"id": 123,
"name": "أبو عمر",
"username": "abu_omar_dev",
"email": "abu.omar@example.com",
"address": {
"street": "شارع القدس",
"city": "نابلس",
"country": "فلسطين"
},
"bio": "مبرمج ومطور برمجيات...",
"followers_count": 5000,
"following_count": 150,
"posts": [ ...قائمة طويلة جدًا من المنشورات... ]
}
الآن، إذا كانت واجهة الموبايل تحتاج فقط لعرض الاسم (name)، فهي مضطرة لتحميل كل هذه البيانات واستهلاك باقة الإنترنت للمستخدم وإبطاء التطبيق بلا أي داعٍ.
مشكلة الـ Under-fetching: عندما تستجدي البيانات
هذه هي المشكلة المعاكسة. تخيل أنك تريد بناء صفحة تعرض المستخدم ومنشوراته وتعليقات المتابعين على كل منشور.
باستخدام REST، ستكون الرحلة كالتالي:
- الطلب الأول:
GET /api/users/123لجلب بيانات المستخدم. - الطلب الثاني:
GET /api/users/123/postsلجلب قائمة منشوراته. - الطلبات التالية (N طلبات): لكل منشور، ستقوم بعمل طلب جديد
GET /api/posts/POST_ID/commentsلجلب التعليقات.
هذه المشكلة تُعرف بـ “N+1 Problem”. أنت تقوم بطلب واحد رئيسي، ثم N من الطلبات الإضافية. هذا العدد الكبير من رحلات الذهاب والإياب بين العميل والخادم يقتل أداء التطبيق، خصوصًا على شبكات الموبايل غير المستقرة.
GraphQL: المنقذ الذي يمنحك قوة الاختيار
GraphQL ليست لغة برمجة، ولا قاعدة بيانات، ولا إطار عمل (Framework) خاص بالـ Backend. ببساطة، GraphQL هي لغة استعلام (Query Language) للـ API الخاص بك، ومواصفة قياسية لكيفية تنفيذ هذه الاستعلامات.
فكر فيها كأنها “بوفيه مفتوح” بدلًا من “قائمة طعام محددة”. مع REST API، أنت تطلب “الوجبة رقم 5”. مع GraphQL، أنت تذهب للبوفيه وتختار بالضبط ما تريده في صحنك: “أريد القليل من الأرز، قطعة دجاج، والكثير من السلطة، وبدون زيتون”.
كيف يعمل السحر؟
بدلاً من وجود عشرات نقاط الوصول (Endpoints)، في GraphQL عادةً ما يكون لديك نقطة وصول واحدة فقط (مثل /graphql). العميل (الواجهة الأمامية) يرسل “استعلامًا” (Query) على شكل نص يصف البيانات التي يريدها بالضبط.
مثال عملي: حل مشكلة الـ Over-fetching
لنفترض أن الواجهة الأمامية تحتاج فقط لاسم المستخدم وصورته. بدلاً من طلب /api/users/123، سترسل استعلام GraphQL كالتالي:
query GetUserProfileHeader {
user(id: "123") {
name
profilePictureUrl
}
}
والسيرفر سيرد بـ JSON يحتوي فقط على ما طلبته، لا أكثر ولا أقل:
{
"data": {
"user": {
"name": "أبو عمر",
"profilePictureUrl": "https://example.com/abu_omar.jpg"
}
}
}
لاحظت كيف؟ الواجهة الأمامية هي التي تتحكم بما يصلها من بيانات. “مش إحنا اللي بنقرر شو نبعثلهم، هم بطلبوا اللي بناسبهم”.
مثال عملي: حل مشكلة الـ Under-fetching
الآن، لصفحة المستخدم الكاملة التي تحتاج لبيانات المستخدم وآخر 3 منشورات له مع عناوينها، يمكن للواجهة الأمامية كتابة استعلام واحد مركب:
query GetUserProfilePage {
user(id: "123") {
name
bio
posts(last: 3) {
id
title
createdAt
}
}
}
والرد سيأتي في طلب واحد فقط، منظم بنفس شكل الطلب:
{
"data": {
"user": {
"name": "أبو عمر",
"bio": "مبرمج ومطور برمجيات...",
"posts": [
{
"id": "post-1",
"title": "مقالتي الجديدة عن GraphQL",
"createdAt": "2023-10-27T10:00:00Z"
},
{
"id": "post-2",
"title": "أفضل ممارسات الذكاء الاصطناعي",
"createdAt": "2023-10-26T15:30:00Z"
},
{
"id": "post-3",
"title": "كيف تبدأ في البرمجة",
"createdAt": "2023-10-25T12:00:00Z"
}
]
}
}
}
بطلب واحد فقط، حصلنا على كل ما نحتاجه. لا رحلات مكوكية، لا بيانات زائدة. كفاءة وسرعة واحترام لباقة الإنترنت الخاصة بالمستخدم.
نصائح عملية من خبرة أبو عمر
تبني GraphQL كان نقلة نوعية في مشاريعنا، لكن الطريق ما كان مفروش بالورود. إليكم بعض النصائح من قلب التجربة:
1. ابدأ بالـ Schema: عقدك المقدس
كل شيء في GraphQL يبدأ بالـ Schema (المخطط). هو العقد الموثق بين الواجهة الأمامية والخلفية. استثمر وقتًا في تصميمه جيدًا. استخدم لغة GraphQL Schema Definition Language (SDL) لتعريف أنواع البيانات المتاحة (Types) والاستعلامات (Queries) والتعديلات (Mutations).
نصيحة شخصية: لا تخف من تعديل الـ Schema. GraphQL مصمم للتطور. بدلاً من إصدار v2 و v3 من الـ API كما في REST، يمكنك ببساطة إضافة حقول جديدة دون التأثير على العملاء القدامى. يمكن إخفاء الحقول القديمة باستخدام
@deprecated.
2. الـ Resolvers هي دماغ العملية
الـ Schema يصف “ماذا” يمكنك أن تطلب، أما الـ Resolvers فهي الدوال التي تجلب البيانات فعليًا، وتجيب على سؤال “كيف”. كل حقل في الـ Schema الخاص بك يقابله Resolver في الكود الخلفي.
هذا يمنحك مرونة هائلة. الـ Resolver الخاص ببيانات المستخدم قد يجلبها من قاعدة بيانات PostgreSQL، بينما الـ Resolver الخاص بمنشوراته قد يجلبها من MongoDB، والـ Resolver الخاص بالطقس قد يستدعي API خارجي آخر. كل هذا يحدث بسلاسة خلف الكواليس.
3. لا تهمل جانب الأمان والأداء
القوة الكبيرة تأتي مع مسؤولية كبيرة. بما أن العميل يمكنه طلب استعلامات معقدة جدًا، قد يستغل أحدهم هذه الميزة لإرسال استعلامات تستهلك كل موارد السيرفر (Denial of Service).
- تحديد عمق الاستعلام (Query Depth Limiting): امنع الاستعلامات المتشعبة جدًا (مثل طلب أصدقاء أصدقاء أصدقاء…).
- استخدام Timeouts: لا تسمح لاستعلام واحد بالعمل إلى الأبد.
– تحليل تكلفة الاستعلام (Query Cost Analysis): أعطِ “تكلفة” لكل حقل، وامنع تنفيذ الاستعلامات التي تتجاوز تكلفتها حدًا معينًا.
4. عالم الأدوات رائع، فاستغله
النظام البيئي لـ GraphQL ضخم وناضج. لا تحاول إعادة اختراع العجلة.
- للـ Backend: استخدم مكتبات مثل Apollo Server (لـ Node.js) أو Strawberry (لـ Python) أو graphql-java (لـ Java). هذه المكتبات توفر لك الكثير من الميزات الجاهزة.
- للـ Frontend: استخدم عملاء مثل Apollo Client أو Relay. هذه المكتبات تسهل إدارة الحالة، التخزين المؤقت (Caching)، والتحديثات الفورية للواجهة. “ما بتغلب حالك، كل شي جاهز ومستنياك”.
الخلاصة: هل يجب أن أستخدم GraphQL؟
GraphQL ليس الحل السحري لكل المشاكل، و REST API ما زال خيارًا ممتازًا للكثير من الحالات البسيطة. لكن، إذا وجدت نفسك في أحد هذه المواقف، فأنصحك بشدة أن تعطي GraphQL فرصة:
- لديك تطبيقات متعددة (ويب، موبايل iOS، موبايل Android) ولكل منها متطلبات بيانات مختلفة.
- واجهات تطبيقك معقدة وتحتاج لبيانات من مصادر متعددة في شاشة واحدة.
- فريق الواجهة الأمامية يشتكي باستمرار من بطء الـ API أو من حاجته لتعديلات مستمرة في الـ Backend.
- تريد تقليل عدد رحلات الشبكة لتحسين أداء التطبيق على الإنترنت البطيء.
بالنسبة لنا، الانتقال لـ GraphQL لم يكن مجرد تغيير تقني، بل كان تغييرًا في ثقافة العمل. لقد أعطى القوة والمرونة لفريق الواجهة الأمامية، وحرر فريق الواجهة الخلفية من طلبات التعديل الصغيرة والمستمرة، والأهم من ذلك، جعل تطبيقاتنا أسرع وأكثر كفاءة. ✨
فيا صديقي المبرمج، لا تخف من تجربة الجديد. أحيانًا، تغيير بسيط في “كيف” تطلب البيانات، يمكن أن يصنع فرقًا كبيرًا في “ماذا” يمكنك أن تبني. يلا، شدّوا حيلكم! 💪