واجهاتي البرمجية كانت إما بخيلة أو مسرفة: كيف أنقذتني GraphQL من جحيم الـ Over-fetching والـ Under-fetching؟

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

قبل كم سنة، كنت شغال على مشروع لتطبيق جوال لمتجر كتب في مدينتنا. فكرة التطبيق كانت بسيطة: عرض الكتب، معلومات عن المؤلفين، ومراجعات القراء. لكن الشيطان يكمن في التفاصيل، كما يقولون. الواجهة الرئيسية للتطبيق كانت هي الكابوس الحقيقي. كنا بحاجة نعرض فيها ثلاث شغلات: معلومات المستخدم اللي مسجل دخوله (اسمه وصورته)، قائمة بالكتب “الأكثر مبيعاً”، وقائمة بـ “آخر الكتب اللي أضافها المستخدم لسلة أمنياته”.

وقتها كنت شغال بتقنية REST API المعتادة، ولقيت حالي قدام خيارين أحلاهما مر:

  1. الخيار الأول: الواجهة “المسرفة” (Over-fetching): أعمل نقطة نهاية (endpoint) وحدة كبيرة اسمها /api/home-screen. هاي النقطة بترجع كل إشي ممكن نحتاجه في الواجهة الرئيسية في طلب واحد. المشكلة؟ بترجع كتل ضخمة من البيانات! يعني عشان أعرض اسم المستخدم وصورته بس، كانت ترجعلي كل تفاصيل حسابه: عنوانه، تاريخ ميلاده، كل طلباته السابقة… إلخ. التطبيق صار بطيء، وبيستهلك باقة الإنترنت على الفاضي.
  2. الخيار الثاني: الواجهة “البخيلة” (Under-fetching): أعمل ثلاث نقاط نهاية منفصلة. وحدة للمستخدم /api/user/me، ووحدة للكتب الأكثر مبيعاً /api/best-sellers، ووحدة لسلة الأمنيات /api/wishlist. وهون صارت المشكلة عكسية. التطبيق صار لازم يبعت ثلاث طلبات مختلفة للخادم عشان يعرض شاشة وحدة! هالشي زاد من تعقيد الكود في التطبيق، وزاد من وقت التحميل الكلي بسبب تعدد الرحلات ذهاباً وإياباً للخادم.

أتذكر وقتها كنت قاعد مع فنجان القهوة، وبحكي لحالي: “يا زلمة، معقول ما في حل وسط؟ معقول ما في طريقة أطلب فيها من الخادم بالضبط اللي بحتاجه، لا زيادة ولا نقصان؟”. ومن هنا بدأت رحلتي مع تقنية غيرت كل مفهومي عن بناء الواجهات البرمجية: GraphQL.

ما هي مشكلة واجهات REST التقليدية؟

قبل ما نغوص في أعماق GraphQL، خلينا نفصّل أكتر المشكلتين اللي واجهتني وبتواجه آلاف المطورين يومياً مع REST APIs التقليدية. القصة كلها بتتلخص في مين اللي عنده “القرار”. في عالم REST، الخادم هو صاحب القرار المطلق. هو اللي بقرر شكل البيانات وحجمها اللي رح ترجعلك في كل طلب.

مشكلة الإفراط في جلب البيانات (Over-fetching)

هذه هي الواجهة “المسرفة” أو الكريمة زيادة عن اللزوم. أنت كمطور للواجهة الأمامية (Frontend) تحتاج معلومة صغيرة، لكن الخادم يرسل لك كل ما يتعلق بهذا الكائن.

مثال عملي: تخيل أنك تريد عرض قائمة بأسماء المستخدمين وصورهم الشخصية فقط. في REST، قد تضطر إلى استدعاء نقطة نهاية مثل GET /api/users. الرد قد يبدو هكذا:


[
  {
    "id": "1",
    "name": "أبو عمر",
    "username": "abu_omar_dev",
    "email": "abu.omar@example.com",
    "address": {
      "street": "شارع القدس",
      "city": "الخليل",
      "zipcode": "P12345"
    },
    "profile_picture_url": "https://example.com/pic1.jpg",
    "bio": "مبرمج ومطور برمجيات...",
    "last_login": "2023-10-27T10:00:00Z"
  },
  {
    "id": "2",
    "name": "علي",
    // ... ونفس كمية البيانات للمستخدم الثاني
  }
]

لاحظ أنك احتجت فقط حقلي name و profile_picture_url، ولكنك استلمت كل تلك البيانات الإضافية (العنوان، البريد الإلكتروني، السيرة الذاتية…). هذا إهدار كبير لموارد الشبكة، خاصة على تطبيقات الجوال التي تعتمد على باقات الإنترنت المحدودة.

مشكلة القصور في جلب البيانات (Under-fetching)

وهذه هي الواجهة “البخيلة”. نقطة النهاية لا تعطيك كل ما تحتاجه من أول مرة، فتضطر إلى مطاردة البيانات عبر طلبات متعددة.

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

باستخدام REST، ستكون الخطوات كالتالي:

  1. الطلب الأول: جلب معلومات المستخدم.

    GET /api/users/1
  2. الطلب الثاني: بعد أن تحصل على معلومات المستخدم، تستخدم هويته (ID) لجلب مقالاته.

    GET /api/users/1/posts

هذه المشكلة، التي تسمى أحياناً “N+1 problem”، تجعل التطبيق بطيئاً ومعقداً. كل طلب إضافي هو رحلة جديدة عبر الشبكة تزيد من وقت الانتظار لدى المستخدم النهائي.

GraphQL: المنقذ الذي طال انتظاره

هنا يأتي دور GraphQL. GraphQL ليست مكتبة برمجية أو إطار عمل بحد ذاتها، بل هي لغة استعلام (Query Language) للواجهات البرمجية، ومعيار معماري تم تطويره في فيسبوك عام 2012 لحل هذه المشاكل بالضبط.

الفكرة الجوهرية في GraphQL هي قلب موازين القوى. بدلاً من أن يكون الخادم هو المتحكم، يصبح العميل (Client) هو من يقرر. العميل يرسل استعلاماً يصف فيه هيكل البيانات التي يحتاجها بدقة متناهية، والخادم يرجع له البيانات بنفس الهيكل المطلوب بالضبط.

باختصار، مع GraphQL “بتطلب على قد حاجتك، لا زيادة ولا نقصان”.

كيف تعمل GraphQL بالضبط؟

لفهم سحر GraphQL، يجب أن نعرف مكوناتها الثلاثة الرئيسية: الاستعلامات (Queries)، المخطط (Schema)، والمُحلِّلات (Resolvers).

1. الاستعلامات (Queries): اطلب ما تريد فقط

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


query GetUserProfileWithPosts {
  user(id: "1") {
    name
    profile_picture_url
    posts(limit: 3) {
      title
      createdAt
    }
  }
}

والأجمل من ذلك، أن الرد الذي سيصل من الخادم سيكون صورة طبق الأصل عن بنية الاستعلام:


{
  "data": {
    "user": {
      "name": "أبو عمر",
      "profile_picture_url": "https://example.com/pic1.jpg",
      "posts": [
        {
          "title": "مقدمة إلى الذكاء الاصطناعي",
          "createdAt": "2023-01-15T..."
        },
        {
          "title": "لماذا GraphQL أفضل من REST؟",
          "createdAt": "2023-02-20T..."
        },
        {
          "title": "بناء أول تطبيق لك",
          "createdAt": "2023-03-10T..."
        }
      ]
    }
  }
}

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


query GetUserName {
  user(id: "1") {
    name
  }
}

هذه المرونة هي قلب قوة GraphQL.

2. المخطط (Schema): العقد بين العميل والخادم

كيف يعرف الخادم ما هي البيانات التي يمكن للعميل طلبها؟ الجواب هو “المخطط” أو الـ Schema. المخطط هو حجر الأساس لأي واجهة GraphQL. إنه بمثابة عقد موثّق يصف كل أنواع البيانات المتاحة، والاستعلامات التي يمكن إجراؤها، والتعديلات (Mutations) التي يمكن تنفيذها.

المخطط مكتوب بلغة خاصة (Schema Definition Language – SDL)، وهو سهل القراءة والفهم. مثال بسيط للمخطط الخاص بمثالنا السابق:


# يصف كائن المستخدم
type User {
  id: ID! # الـ ! تعني أن هذا الحقل إجباري
  name: String
  username: String
  email: String
  profile_picture_url: String
  posts(limit: Int): [Post] # يمكن للمستخدم أن يكون لديه قائمة من المقالات
}

# يصف كائن المقال
type Post {
  id: ID!
  title: String
  content: String
  createdAt: String
  author: User # يمكن للمقال أن يكون له مؤلف
}

# يصف كل الاستعلامات المتاحة للقراءة
type Query {
  users: [User]
  user(id: ID!): User
  posts: [Post]
}

هذا المخطط يعمل كتوثيق حي ومباشر للـ API. أي مطور يمكنه النظر إليه ليعرف بالضبط ما يمكنه طلبه.

3. المُحلِّلات (Resolvers): من أين تأتي البيانات؟

حتى الآن، كل ما لدينا هو وصف للبيانات (Schema) وطريقة لطلبها (Query). لكن من أين تأتي البيانات الفعلية؟ هنا يأتي دور “المُحلِّلات” أو الـ Resolvers.

المُحلِّل هو عبارة عن دالة (function) برمجية على الخادم مسؤولة عن جلب البيانات لحقل معين في المخطط. لكل حقل في الـ Schema، هناك resolver مقابل له.

عندما يستقبل الخادم استعلام GraphQL، فإنه يمر على كل حقل مطلوب في الاستعلام وينفذ الـ resolver الخاص به. مثال بسيط باستخدام JavaScript (Node.js):


const resolvers = {
  // الـ Resolvers الخاصة بالاستعلامات الرئيسية
  Query: {
    // Resolver لجلب مستخدم معين
    user: (parent, args) => {
      // args تحتوي على الـ id الذي تم تمريره في الاستعلام
      // هنا نكتب الكود الذي يجلب المستخدم من قاعدة البيانات
      return database.users.findById(args.id);
    },
  },

  // الـ Resolvers الخاصة بحقول كائن المستخدم
  User: {
    // Resolver لجلب مقالات المستخدم
    posts: (user, args) => {
      // user هو الكائن الأب (المستخدم الذي تم جلبه في الخطوة السابقة)
      // args تحتوي على الـ limit الذي تم تمريره
      // هنا نكتب الكود الذي يجلب المقالات المرتبطة بهذا المستخدم
      return database.posts.findByUserId(user.id, { limit: args.limit });
    }
  }
};

الجميل في هذا النموذج هو أن كل resolver مستقل بذاته. الـ resolver الخاص بالمستخدم يمكنه جلب البيانات من قاعدة بيانات MySQL، بينما الـ resolver الخاص بالمقالات يمكنه جلبها من قاعدة بيانات MongoDB أو حتى من واجهة REST API أخرى! GraphQL توحد كل مصادر البيانات هذه خلف واجهة واحدة متناسقة.

نصائح من مطبخ أبو عمر البرمجي

بعد سنوات من العمل مع GraphQL، تعلمت بعض الدروس بالطريقة الصعبة أحياناً. اسمحوا لي أن أشارككم بعض النصائح العملية:

  • ابدأ بالمخطط أولاً (Schema-First Design): قبل كتابة سطر كود واحد في الـ resolvers، اجلس مع فريقك وصمموا الـ Schema. “فكّر قبل ما تكتب الكود”. هذه العملية تجبركم على التفكير في نموذج البيانات الخاص بكم بشكل واضح ومنطقي، وتمنع الكثير من المشاكل لاحقاً.
  • لا تتخلص من REST فوراً: إذا كان لديك نظام قائم يعتمد على REST APIs، لست مضطراً لإعادة بناء كل شيء من الصفر. يمكنك بناء طبقة GraphQL فوق واجهات REST القديمة. الـ resolvers في هذه الحالة ستقوم ببساطة باستدعاء نقاط النهاية القديمة وتجميع البيانات. هذه استراتيجية ممتازة للترحيل التدريجي.
  • انتبه لمشكلة N+1 في GraphQL: نعم، GraphQL تحل مشكلة الـ N+1 من جهة العميل، لكنها قد تسببها من جهة الخادم! تخيل أنك طلبت قائمة من 10 مستخدمين مع مقالات كل منهم. إذا كان الـ resolver الخاص بالمقالات يقوم بعمل استعلام جديد لقاعدة البيانات لكل مستخدم، فسينتهي بك الأمر بـ 11 استعلاماً لقاعدة البيانات! الحل هنا هو استخدام تقنية اسمها “Dataloader” التي تقوم بتجميع الطلبات المتشابهة في دفعة واحدة (batching) وذاكرة تخزين مؤقت (caching).
  • فكر في الأمان والتصريح (Authorization): GraphQL لا تقدم حلاً سحرياً للأمان. أنت المسؤول عن التحقق من صلاحيات المستخدم. أفضل مكان للقيام بذلك هو داخل الـ resolvers أو عبر طبقة وسيطة (middleware). يمكنك تمرير معلومات المستخدم الحالي عبر كائن الـ `context` والتحقق من صلاحياته قبل إرجاع أي بيانات حساسة.

الخلاصة: هل GraphQL هي الحل السحري؟ 🚀

بعد كل هذا الكلام، هل GraphQL هي الحل النهائي لكل مشاكل بناء الواجهات البرمجية؟ الجواب هو: لا يوجد حل سحري في عالم البرمجة.

GraphQL هي أداة قوية بشكل لا يصدق، وقد أنقذتني شخصياً من جحيم الواجهات “البخيلة” و”المسرفة”. إنها تمنح قوة هائلة لمطوري الواجهات الأمامية، وتجعل تطوير التطبيقات المعقدة التي تستهلك بيانات من مصادر متعددة (مثل تطبيقات الموبايل والـ Single Page Applications) أسهل وأكثر كفاءة.

لكن هذه القوة تأتي مع ثمن. منحنى التعلم لـ GraphQL أعلى من REST، والتعقيد ينتقل من العميل إلى الخادم. أنت بحاجة إلى إدارة المخطط، كتابة الـ resolvers، والتعامل مع قضايا الأداء مثل مشكلة N+1.

نصيحتي الأخيرة لك: إذا كنت تبني تطبيقاً معقداً له واجهات عميل متعددة (ويب، موبايل، تلفاز ذكي…)، فاستثمار الوقت في تعلم GraphQL سيعود عليك بفائدة عظيمة على المدى الطويل. أما إذا كان مشروعك بسيطاً، مثلاً واجهة CRUD بسيطة، فقد تكون REST API التقليدية كافية ومناسبة أكثر.

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

والله ولي التوفيق.

أبو عمر

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

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

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

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

آخر المدونات

خوارزميات

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

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

31 مارس، 2026 قراءة المزيد
تسويق رقمي

ميزانيتي الإعلانية كانت بئراً بلا قرار: كيف أنقذني ‘تتبع التحويلات’ من جحيم الإنفاق الأعمى؟

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

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

مكوناتي كانت فوضى: كيف أنقذني نظام التصميم (Design System) من جحيم التناقض البصري؟

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

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

كنت سجينًا لدى مزود سحابي واحد: كيف حررتني استراتيجية ‘السحابة المتعددة’ (Multi-Cloud) من جحيم الاعتمادية المطلقة؟

أشارككم قصتي مع "الاعتمادية المطلقة" على مزود سحابي واحد، وكيف كانت استراتيجية السحابة المتعددة (Multi-Cloud) طوق النجاة الذي حررني. هذه المقالة دليل عملي للمطورين والشركات...

31 مارس، 2026 قراءة المزيد
التوسع والأداء العالي والأحمال

خادمي الوحيد كان يختنق: كيف أنقذتني ‘موازنة الأحمال’ من جحيم نقطة الفشل الواحدة؟

أشارككم قصتي مع انهيار تطبيقي الأول تحت ضغط المستخدمين، وكيف كانت "موازنة الأحمال" (Load Balancing) هي طوق النجاة. دليل عملي ومفصل للمطورين لفهم وتطبيق هذا...

31 مارس، 2026 قراءة المزيد
التكنلوجيا المالية Fintech

بياناتي المالية كانت سجينة في قلاع مصرفية: كيف حررتني واجهات ‘الخدمات المصرفية المفتوحة’ (Open Banking)؟

أروي لكم حكايتي كمبرمج، "أبو عمر"، وكيف انتقلت من جحيم محاولة تجميع بياناتي المالية من بنوك متفرقة إلى عالم "الخدمات المصرفية المفتوحة" (Open Banking) السهل...

30 مارس، 2026 قراءة المزيد
اختبارات الاداء والجودة

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

أشارككم قصة حقيقية عن إطلاق كارثي لتطبيق كنت أظنه مثالياً، وكيف تعلمت بالطريقة الصعبة أهمية اختبار الإجهاد (Stress Testing). هذه المقالة هي دليلك العملي لتجنب...

30 مارس، 2026 قراءة المزيد
أدوات وانتاجية

طرفيتي كانت بئرًا بلا قرار: كيف أنقذتني أدوات مثل ‘fzf’ و ‘zsh’ من جحيم البحث عن الإبرة في كومة قش؟

أشارككم تجربتي كـ "أبو عمر"، مبرمج فلسطيني، في تحويل الطرفية (Terminal) من كابوس مربك إلى أداة إنتاجية خارقة. اكتشفوا كيف أنقذتني أدوات مثل zsh و...

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