واجهاتنا البرمجية كانت غير مرنة: كيف أنقذنا GraphQL من جحيم الجلب المفرط والناقص للبيانات؟

يا أهلاً وسهلاً فيكم جميعاً، معكم أخوكم أبو عمر.

قبل كم سنة، كنا شغالين على مشروع كبير، منصة تواصل اجتماعي جديدة. الفريق كان مليان حماس والطموحات عالية، وأنا كنت مسؤول عن فريق الواجهات الخلفية (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) وتعدد الطلبات

هذه هي المشكلة المعاكسة تماماً. الجلب الناقص يعني أن نقطة النهاية لا توفر كل البيانات التي تحتاجها، مما يضطرك لإرسال طلبات إضافية. في مثال فريق الويب، لعرض صفحة المستخدم ومنشوراته، كانوا بحاجة لـ:

  1. إرسال طلب GET /api/users/{id} للحصول على معلومات المستخدم.
  2. بعد وصول الرد، إرسال طلب آخر 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 فرصة حقيقية. ابدأوا بمشروع جانبي صغير، جربوا بأنفسكم، وشوفوا الفرق.

يلا يا جماعة، شدّوا حيلكم، عالم البرمجة واسع وجميل ومليء بالحلول المبتكرة. 🚀

أبو عمر

سجل دخولك لعمل نقاش تفاعلي

كافة المحادثات خاصة ولا يتم عرضها على الموقع نهائياً

آراء من النقاشات

لا توجد آراء منشورة بعد. كن أول من يشارك رأيه!

آخر المدونات

​معمارية البرمجيات

خدماتنا كانت في علاقة سامة: كيف أنقذتنا ‘المعمارية القائمة على الأحداث’ (EDA) من جحيم الاقتران الخانق؟

أشارككم قصة حقيقية من قلب المعركة البرمجية، يوم كاد "الاقتران الخانق" بين خدماتنا أن يدمر إطلاقاً مهماً. اكتشفوا كيف كانت "المعمارية القائمة على الأحداث" (EDA)...

13 أبريل، 2026 قراءة المزيد
ذكاء اصطناعي

نماذجنا اللغوية كانت تهلوس: كيف أنقذنا التوليد المعزز بالاسترجاع (RAG) من جحيم المعلومات الخاطئة؟

أشارككم قصة حقيقية عن "هلوسة" الذكاء الاصطناعي وكيف تسببت في مشكلة حقيقية لأحد عملائنا. اكتشفوا كيف أنقذتنا تقنية التوليد المعزز بالاسترجاع (RAG) من خلال ربط...

13 أبريل، 2026 قراءة المزيد
تجربة المستخدم والابداع البصري

واجهاتنا كانت قمرة قيادة لطائرة حربية: كيف أنقذنا ‘تقليل الحمل المعرفي’ من جحيم إرهاق المستخدمين؟

بتذكر مرة كُنا نبني لوحة تحكم معقدة، وصارت زي قمرة قيادة طائرة حربية من كثرة الأزرار والمؤشرات. في هذه المقالة، بحكي لكم كيف اكتشفنا مفهوم...

13 أبريل، 2026 قراءة المزيد
برمجة وقواعد بيانات

بحثنا كان يزحف كالسلحفاة: كيف أنقذتنا ‘فهارس قاعدة البيانات’ (Database Indexing) من جحيم المسح الكامل للجدول؟

أشارككم قصة حقيقية عن مشروع كاد أن يفشل بسبب بطء كارثي، وكيف كانت "فهارس قواعد البيانات" هي المنقذ الذي حول زحف السلحفاة إلى سرعة البرق....

13 أبريل، 2026 قراءة المزيد
الحوسبة السحابية

بنيتنا التحتية كانت قصورًا من رمال: كيف أنقذتنا ‘البنية التحتية كشيفرة’ (IaC) من جحيم الانحراف في الإعدادات؟

أنا أبو عمر، وأهلاً بكم في مقالة جديدة. دعوني أحكي لكم قصة عن ليلة خميس كادت أن تدمر مشروعاً كاملاً بسبب تغيير يدوي بسيط، وكيف...

13 أبريل، 2026 قراءة المزيد
البودكاست