قصة فنجان قهوة وتطبيق بطيء
يا جماعة الخير، السلام عليكم. معكم أخوكم أبو عمر.
قبل كم سنة، كنت شغال على مشروع داشبورد (لوحة تحكم) لعميل مهم. كانت الفكرة بسيطة: صفحة رئيسية تعرض للمستخدم معلوماته الشخصية، آخر مقالات كتبها، قائمة بأصدقائه، وآخر التعليقات على مقالاته. “زي الليرة”، قلت لحالي، الشغل مرتب والـ API endpoints اللي بنيتها باستخدام REST كانت نظيفة ومقسمة بشكل منطقي.
GET /api/user/{id}لجلب معلومات المستخدم.GET /api/user/{id}/postsلجلب مقالاته.GET /api/user/{id}/friendsلجلب أصدقائه.GET /api/posts/{postId}/commentsلجلب التعليقات على كل مقال… وهكذا.
كنت فخور بالشغل، والـ backend مقسم “ع المسطرة”. شغّلت المشروع على جهازي اللي اتصاله بالإنترنت سريع، وكل شيء كان يشتغل بسرعة ممتازة. سلمت الشغل للعميل وأنا مبسوط. بعد يومين، بيجيني اتصال من العميل: “أبو عمر، يعطيك العافية، بس الداشبورد بطييييء جدًا، الصفحة بتضلها تحمل شوي شوي”.
فتحت الـ Developer Tools في المتصفح على تاب الـ Network، وحطيت محاكاة لاتصال 3G بطيء… وهون كانت الصدمة. شفت الطلبات بتنزل ورا بعضها زي شلال المي، واحد بستنى الثاني ليخلص. 💧
طلبت معلومات المستخدم، وبعد ما وصلت، طلبت مقالاته، وبعد ما وصلت، بلشت أطلب التعليقات لكل مقال… والمجموع كان حوالي 10 طلبات منفصلة عشان أعرض صفحة واحدة! أدركت وقتها إني وقعت في فخ كلاسيكي اسمه “شلالات الشبكة” أو Network Waterfalls.
ما هي “شلالات الشبكة” (Network Waterfalls) وليش هي كارثة؟
ببساطة، شلال الشبكة هو سلسلة من طلبات الشبكة (API calls) اللي بتعتمد على بعضها. يعني، ما بتقدر تبدأ الطلب “ب” إلا لما يرجعلك جواب الطلب “أ”.
تخيل إنك بتبني حيط، ما بتقدر تحط الطوبة الثانية إلا لما الأولى تكون ثبتت مكانها، وما بتقدر تحط الثالثة إلا لما الثانية تثبت. كل عملية انتظار بتضيف وقت، وفي عالم الويب وتطبيقات الموبايل، كل ميللي ثانية بتفرق.
تشريح المشكلة مع REST API
معمارية REST التقليدية، رغم قوتها وجمالها في تنظيم الـ backend، بتشجع على هاد النمط بدون قصد. ليش؟ لأنها مبنية على فكرة “المصادر” (Resources). كل مصدر إله endpoint خاص فيه.
في قصتي، كان عندي المصادر التالية: User, Post, Comment, Friend.
عشان أعرض الصفحة، كنت بحاجة لـ:
- أجيب بيانات الـ
User. - من بيانات الـ
User، آخذ الـ ID وأجيب الـPostsتبعونه. - من بيانات الـ
User، آخذ الـ ID وأجيب الـFriendsتبعونه. - لكل
Postجبته، آخذ الـ ID وأجيب الـCommentsالخاصة فيه.
هذا الأسلوب بيخلق مشكلتين رئيسيتين:
- Under-fetching (الجلب الناقص): أول طلب بجيبلك بس معلومات المستخدم، بس أنت محتاج كمان مقالاته وأصدقائه، فبتضطر تعمل طلبات إضافية.
- Over-fetching (الجلب الزائد): أحيانًا، endpoint معين برجعلك بيانات أكثر من اللي بتحتاجها. يمكن
GET /api/user/{id}برجع 50 حقل عن المستخدم، وأنت كل اللي بدك إياه هو اسمه وصورته الشخصية. هاي بيانات إضافية بتسافر عبر الشبكة بدون أي داعي.
المحصلة: تطبيق بطيء وتجربة مستخدم سيئة، خصوصًا على اتصالات الإنترنت الضعيفة اللي بنعاني منها في كثير من بلادنا.
الحل السحري: كيف دخلت GraphQL على الخط 🚀
بعد ما تشخصت المشكلة، بلشت أبحث عن حلول. كنت أسمع عن تقنية اسمها GraphQL بس ما عمري تعمقت فيها. قلت لحالي، “يلا يا أبو عمر، وقته نتعلم إشي جديد”. وهون كانت نقطة التحول.
شو هي GraphQL أصلاً؟
GraphQL هي لغة استعلام (Query Language) للـ API تبعك، وكمان بيئة تشغيل في السيرفر لتنفيذ هاي الاستعلامات. الفكرة العبقرية فيها هي إنها بتغير موازين القوى.
بدل ما السيرفر يقرر شكل البيانات اللي برجعها، الـ Client (العميل/المتصفح) هو اللي بطلب شكل البيانات اللي بده إياها بالزبط، لا زيادة ولا نقصان، وكل هاد بطلب واحد فقط!
خليني أرجع لمثال المطعم:
- REST API: زي لما تطلب وجبة “Set Menu”. يمكن تجيك الشوربة والطبق الرئيسي والسلطة والحلو، حتى لو أنت بس بدك الطبق الرئيسي. (Over-fetching). أو لازم تطلب كل صحن لحال. (Under-fetching/Waterfall).
- GraphQL: زي لما تطلب من الشيف طلب مخصص: “بدي قطعة ستيك medium-well، مع شوية خضار سوتيه، وبطاطا مهروسة بدون زبدة”. بتوصف طلبك بالكامل، وبوصلك على صحن واحد زي ما بدك بالزبط.
تطبيق الحل على مشكلتنا (أمثلة كود)
خلينا نشوف الفرق بشكل عملي. في السابق، الكود في الـ frontend كان ممكن يشبه هيك (باستخدام JavaScript كمثال):
// قبل GraphQL: شلال من طلبات REST
async function getDashboardData_REST(userId) {
try {
// الطلب الأول
const userRes = await fetch(`/api/user/${userId}`);
const user = await userRes.json();
// الطلب الثاني (يعتمد على الأول)
const postsRes = await fetch(`/api/user/${userId}/posts`);
const posts = await postsRes.json();
// الطلب الثالث (يعتمد على الأول)
const friendsRes = await fetch(`/api/user/${userId}/friends`);
const friends = await friendsRes.json();
// ... وتخيل كمان 7 طلبات للتعليقات وغيرها!
// بالنهاية، ندمج كل البيانات لعرض الصفحة
console.log("Done fetching, finally!");
return { user, posts, friends };
} catch (error) {
console.error("Failed to fetch data:", error);
}
}
هسا، شوفوا كيف صار الوضع مع GraphQL. أولاً، بنكتب “استعلام” واحد يوصف كل البيانات اللي بنحتاجها:
# استعلام GraphQL واحد يطلب كل شيء
query GetDashboardData($userId: ID!) {
user(id: $userId) {
name
profilePicture
posts(first: 5) {
title
createdAt
comments(first: 2) {
body
author {
name
}
}
}
friends(first: 8) {
name
profilePicture
}
}
}
لاحظوا كيف الاستعلام بيشبه بنية JSON. أنت بتطلب الحقول اللي بدك إياها بالزبط. بدك 5 مقالات بس؟ بتحدد (first: 5). بدك بس اسم وصورة الصديق؟ بتطلب بس name و profilePicture.
والكود في الـ frontend بصير أبسط بكثير:
// بعد GraphQL: طلب واحد فقط!
async function getDashboardData_GraphQL(userId) {
const query = `
query GetDashboardData($userId: ID!) {
user(id: $userId) {
name
profilePicture
posts(first: 5) {
# ... باقي الاستعلام
}
friends(first: 8) {
# ... باقي الاستعلام
}
}
}
`;
try {
const res = await fetch('/graphql', { // كل الطلبات بتروح لنفس الـ endpoint
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
query,
variables: { userId }, // بنمرر الـ ID كمتغير
}),
});
const { data } = await res.json();
console.log("Done fetching, that was fast!");
return data; // 'data' يحتوي على كل شيء جاهز للعرض
} catch (error) {
console.error("Failed to fetch data:", error);
}
}
النتيجة؟ طلب شبكة واحد، استجابة واحدة تحتوي على كل البيانات اللي طلبتها بالضبط. شلال الشبكة تبخر. والأداء تحسن بشكل خرافي.
نصائح من خبرة أبو عمر 💡
بعد ما اشتغلت على مشاريع كثيرة باستخدام GraphQL، تعلمت كم شغلة بحب أشاركم إياها.
مش كل إشي بده GraphQL
صحيح أنا بحب GraphQL، بس هي مش الحل لكل المشاكل. “ما في داعي تجيب بازوكا عشان تقتل دبّانة”. إذا كان عندك تطبيق بسيط جدًا، أو مايكروسيرفس عنده وظيفة محددة جدًا (مثلاً، خدمة إرسال إيميلات)، يمكن REST API يكون أسرع وأسهل في التنفيذ. GraphQL بتلمع وبتظهر قوتها الحقيقية في التطبيقات المعقدة اللي فيها بيانات متشابكة (زي الشبكات الاجتماعية، لوحات التحكم، تطبيقات التجارة الإلكترونية الكبيرة) أو لما يكون عندك عدة عملاء (تطبيق ويب، تطبيق موبايل iOS، تطبيق أندرويد) وكل واحد فيهم بيحتاج بيانات مختلفة.
فكر في “العميل” أولاً (Client-First)
أجمل شيء في GraphQL إنها بتجبرك كمطور backend تفكر من وجهة نظر مطور الـ frontend أو الموبايل. بدل ما تفكر “شو المصادر اللي عندي؟”، بتصير تفكر “شو الشاشات اللي عندي وكل شاشة شو البيانات اللي بتحتاجها؟”. هذا التحول في طريقة التفكير بحد ذاته بيؤدي لتصميم API أفضل وأكثر كفاءة.
انتبه لمشكلة N+1 في الـ Backend
هاي نصيحة ذهبية ومهمة جدًا. GraphQL بتحل مشكلة شلال الشبكة من جهة العميل (Client)، لكنها ممكن تسبب مشكلة مشابهة في جهة الخادم (Server) مع قاعدة البيانات إذا ما انتبهت. هاي المشكلة اسمها “N+1 Query Problem”.
تخيل الاستعلام السابق: أنت طلبت مستخدم واحد ومقالاته الخمسة. ممكن السيرفر ينفذ:
- استعلام واحد لجلب المستخدم (
SELECT * FROM users WHERE id = ?) - ثم 5 استعلامات، واحد لكل مقال، لجلب التعليقات تبعته! (
SELECT * FROM comments WHERE post_id = ?)
هيك بصير عندك 1+5 = 6 استعلامات لقاعدة البيانات. الحل لهي المشكلة هو نمط برمجي اسمه DataLoader. فكرته بسيطة: هو بيجمع كل طلبات البيانات المتشابهة خلال دورة حياة الطلب الواحد، وبنفذها مرة واحدة في قاعدة البيانات. بدل 5 استعلامات للتعليقات، بيعمل استعلام واحد: SELECT * FROM comments WHERE post_id IN (1, 2, 3, 4, 5). استخدام DataLoader ضروري جدًا في أي تطبيق GraphQL حقيقي.
الخلاصة: هل أرمي REST API في الزبالة؟
بالتأكيد لا! REST ما زالت تقنية عظيمة وهي أساس الويب الحديث. “كل مطرقة إلها مسمارها”. الفكرة هي إنك توسع صندوق أدواتك 🧰 كمبرمج.
GraphQL أداة قوية جدًا لحل نوع معين من المشاكل، خصوصًا مشاكل الأداء المتعلقة بالشبكة في الواجهات الأمامية المعقدة. هي مش بديل كامل لـ REST، بل هي خيار آخر موجود عندك.
نصيحتي الأخيرة لك: في المرة الجاي اللي بتلاقي حالك بتكتب كود frontend بيعمل سلسلة من طلبات الـ API عشان يعرض واجهة واحدة، تذكر قصة أبو عمر وفنجان القهوة. يمكن يكون هذا هو الوقت المناسب لتبدأ رحلتك مع GraphQL. صدقني، رح تدعيلي. 😉
يعطيكم ألف عافية.