مقدمة: قصة فنجان قهوة وكابوس الـ Endpoints
يا جماعة، السلام عليكم. معكم أخوكم أبو عمر.
قبل كم سنة، كنت ماسك مشروع كبير شوي، تطبيق تواصل اجتماعي فيه كل “الحبشتكنات” اللي ممكن تتخيلوها: بروفايلات مستخدمين، منشورات، تعليقات، إعجابات، رسائل، مجموعات… القائمة تطول. في البداية، كنا شغالين زي الليرة على معمارية REST API التقليدية. الأمور كانت تمام، وكل إشي ماشي حسب الأصول.
كنت أقضي صباحي مع فنجان القهوة وأنا أكتب الـ endpoints الجديدة. /users عشان تجيب المستخدمين، /posts عشان المنشورات، /users/:id/posts عشان تجيب منشورات مستخدم معين. شعور بالإنجاز، صح؟
لكن مع الوقت، كبر المشروع، وصار معنا فريق لتطبيق الموبايل (iOS و Android) وفريق للويب. وهنا بلش وجع الراس. فريق الويب بده يعرض اسم المستخدم وصورته وآخر 3 منشورات إله في الصفحة الرئيسية. فريق الموبايل بده يعرض بس اسم المستخدم وصورته في قائمة الأصدقاء. فريق تاني بده إحصائيات عن عدد الإعجابات والتعليقات لكل منشور.
فجأة، لقيت حالي بغرق. كل يوم طلبات جديدة: “أبو عمر، ممكن تعمللنا endpoint جديدة بس بترجع عدد التعليقات؟ الـ endpoint الحالية بترجع كل التعليقات وهذا بطيء عالموبايل”. “أبو عمر، بدنا نعدل الـ endpoint تبعت البروفايل عشان تضيف تاريخ الميلاد، بس ما بدنا إياها تظهر في نسخة الويب”.
صرت أعمل endpoints مخصصة لكل شاشة ولكل جهاز: /users/:id/summary، /posts/:id/stats، /mobile/v2/feed. الكود صار عبارة عن متاهة من الـ endpoints المتشابهة والمكررة، وكل تعديل صغير في مكان كان يتطلب تعديلات في 5 أماكن ثانية. حسيت إني مش مبرمج، حسيت إني “ترزي” بقعد أفصّل endpoints على مقاس كل واحد. وفي ليلة من الليالي، وأنا براجع كمية الـ endpoints اللي عملتها، قلت لحالي: “لهون وبس، لازم في حل أحسن”. وهون كانت بداية رحلتي مع GraphQL.
ما هي مشكلة REST API بالضبط؟
قبل ما نحكي عن المنقذ GraphQL، خلينا نفهم أصل المشكلة مع REST. معمارية REST عظيمة، وبسيطة، وخدمتنا لسنوات. فكرتها قائمة على “المصادر” (Resources). كل إشي هو مصدر، ولكل مصدر عنوان (URL) خاص فيه. لكن هاي البساطة هي نفسها اللي بتخلق مشاكل في التطبيقات المعقدة.
المشكلة الأولى: الجلب الزائد للبيانات (Over-fetching)
لما تطلب بيانات مستخدم من خلال endpoint مثل GET /api/users/1، السيرفر بقرر شو البيانات اللي رح يرجعها. غالبًا، رح يرجعلك كل معلومات المستخدم الموجودة في قاعدة البيانات.
// Request
GET /api/users/1
// Response
{
"id": 1,
"name": "أبو عمر",
"email": "abu.omar@example.com",
"birthdate": "1985-01-15T00:00:00.000Z",
"address": "القدس، فلسطين",
"createdAt": "2020-05-10T12:00:00.000Z",
"bio": "مبرمج يحب القهوة والكود النظيف."
}
لكن ماذا لو كانت واجهة المستخدم تحتاج فقط لعرض اسم المستخدم (name)؟ أنت هيك استهلكت بيانات (bandwidth) وحملت بيانات ما إلها لزوم (birthdate, address, …إلخ). تخيل هذا على تطبيق موبايل بيستخدم باقة الإنترنت المحدودة، مشكلة!
المشكلة الثانية: الجلب الناقص للبيانات (Under-fetching)
هاي المشكلة هي الوجه الآخر للعملة. لنفرض إنك بدك تعرض صفحة بروفايل المستخدم مع آخر 5 منشورات إله. باستخدام REST، أنت مضطر تعمل طلبين (أو أكثر) للسيرفر:
- الطلب الأول: جلب معلومات المستخدم.
GET /api/users/1 - الطلب الثاني: جلب منشورات هذا المستخدم.
GET /api/users/1/posts?limit=5
هذا يعني رحلة ذهاب وإياب مرتين بين العميل (Client) والخادم (Server)، مما يزيد من وقت التحميل ويؤثر على تجربة المستخدم. هذه المشكلة تُعرف أحيانًا بمشكلة “N+1 requests”.
GraphQL: طوق النجاة الذي كنت أبحث عنه
GraphQL مش لغة برمجة، ولا هي مكتبة، ولا إطار عمل. هي لغة استعلام (Query Language) لواجهات برمجة التطبيقات (APIs)، وكمان هي بيئة تنفيذ (Runtime) على الخادم لتلبية هذه الاستعلامات.
الفكرة عبقرية وبسيطة: بدل ما يكون عندك عشرات الـ endpoints اللي الخادم بحدد شكل استجابتها، بكون عندك endpoint واحدة فقط (عادة /graphql). العميل (Client) هو اللي بقرر شكل البيانات اللي بده إياها بالضبط، وبيرسل استعلام (Query) يوصف هاي البيانات.
نصيحة من أبو عمر: فكر في REST كأنك بتطلب وجبة من مطعم بقائمة ثابتة (Set Menu). رح تجيك الوجبة كاملة مع المقبلات والطبق الجانبي، حتى لو ما بدك إياهم. أما GraphQL، فهو أشبه بالبوفيه المفتوح، بتروح وبتعبي صحنك بس بالأشياء اللي بتحبها وبالكمية اللي بدك إياها.
كيف تعمل GraphQL؟ مثال عملي
لنفترض نفس السيناريو السابق: بدنا نعرض اسم المستخدم ومنشوراته الخمسة الأخيرة. مع GraphQL، العميل بيرسل طلب POST واحد للـ endpoint الوحيدة /graphql، وفي جسم الطلب (body) بيحط الاستعلام التالي:
query GetUserWithPosts {
user(id: 1) {
name
posts(limit: 5) {
title
createdAt
}
}
}
والسيرفر رح يرجع استجابة JSON بنفس شكل الاستعلام بالضبط، لا زيادة ولا نقصان:
{
"data": {
"user": {
"name": "أبو عمر",
"posts": [
{
"title": "مقدمة إلى GraphQL",
"createdAt": "2023-10-27T10:00:00.000Z"
},
{
"title": "لماذا أحب Vim",
"createdAt": "2023-10-25T14:30:00.000Z"
},
// ... 3 more posts
]
}
}
}
شايفين الجمال؟ طلب واحد، جاب كل البيانات اللي بدنا إياها بالضبط. لا over-fetching ولا under-fetching. الواجهة الأمامية (Frontend) صارت هي المتحكمة، وفريق الواجهة الخلفية (Backend) مثلي، صار يركز على توصيف البيانات المتاحة (Schema) وتوفيرها، بدل ما يركز على بناء endpoints مخصصة لكل شاشة.
المكونات الأساسية لـ GraphQL
عشان تفهم السحر اللي بصير، لازم تعرف 3 مفاهيم أساسية:
1. المخطط (Schema)
الـ Schema هو قلب أي GraphQL API. هو العقد بين العميل والخادم. أنت بتوصف فيه كل أنواع البيانات اللي ممكن العميل يطلبها، وكيفية ارتباطها ببعضها. هذا المخطط مكتوب بلغة اسمها Schema Definition Language (SDL).
مثال على مخطط بسيط للمستخدم والمنشور:
# يمثل مستخدم في النظام
type User {
id: ID!
name: String!
email: String
posts: [Post!]
}
# يمثل منشور كتبه مستخدم
type Post {
id: ID!
title: String!
content: String
author: User!
createdAt: String
}
# النقطة اللي بتبدأ منها كل الاستعلامات
type Query {
user(id: ID!): User
posts: [Post!]
}
الـ Schema موثق ذاتيًا (self-documenting). أي مطور جديد بيقدر يقرأه ويفهم كل إمكانيات الـ API فورًا. هذا إشي مرتب جدًا!
2. الاستعلامات (Queries)
كما رأينا في المثال السابق، الـ Queries هي الطريقة اللي العميل بطلب فيها البيانات للقراءة. مرونتها عالية جدًا، والعميل بحدد الحقول اللي بده إياها بالضبط.
3. التعديلات (Mutations)
طيب، كيف بنعدل البيانات (إنشاء، تحديث، حذف)؟ هنا يأتي دور الـ Mutations. هي تشبه الـ Queries في تركيبتها، لكنها مخصصة لعمليات الكتابة. بالعادة، بنحطها تحت نوع خاص اسمه Mutation في الـ Schema.
type Mutation {
createPost(title: String!, content: String): Post
updateUser(id: ID!, name: String): User
}
ولاستخدامها، يرسل العميل طلب Mutation كالتالي:
mutation CreateNewPost {
createPost(title: "مقالتي الجديدة", content: "محتوى المقالة...") {
id
title
}
}
لاحظ أن الـ Mutation بترجع كمان بيانات، فبتقدر تطلب البيانات تبعت العنصر الجديد اللي أنشأته في نفس الطلب.
نصائح عملية من خبرة أبو عمر
بعد ما اشتغلت على GraphQL في عدة مشاريع، جمعتلكم شوية نصائح من القلب:
- GraphQL ليست الحل لكل المشاكل: إذا كان مشروعك بسيطًا (مدونة شخصية، صفحة هبوط)، فغالبًا REST API ستكون أسهل وأسرع في التنفيذ. GraphQL تظهر قوتها الحقيقية في التطبيقات الكبيرة والمعقدة ذات العلاقات المتشعبة بين البيانات والعملاء المتعددين (ويب، موبايل، …إلخ).
- ابدأ بالتدريج: مش ضروري تهدم كل الـ REST API اللي عندك. يمكنك بناء طبقة GraphQL فوق واجهات REST الحالية. هذا يسمى “GraphQL Wrapper”. هكذا، يستفيد فريق الـ Frontend من مزايا GraphQL فورًا، بينما تقوم أنت بترحيل المنطق تدريجيًا في الخلفية.
- استخدم الأدوات المناسبة: عالم GraphQL مليء بالأدوات الرائعة. في جهة الخادم، مكتبات مثل Apollo Server (لـ Node.js) تسهل عليك بناء الخادم. وفي جهة العميل، مكتبات مثل Apollo Client و Relay توفر ميزات مذهلة مثل إدارة الحالة (State Management) والتخزين المؤقت (Caching).
- انتبه للأمان والأداء: القوة الكبيرة تأتي مع مسؤولية كبيرة. بما أن العميل يستطيع طلب ما يشاء، قد يرسل استعلامًا معقدًا جدًا يستهلك موارد الخادم (مثل طلب مستخدم، ثم كل أصدقائه، ثم كل أصدقاء أصدقائه!). لحل هذا، يجب استخدام تقنيات مثل تحديد عمق الاستعلام (Query Depth Limiting)، وتحديد تعقيد الاستعلام (Query Complexity Analysis)، وبالطبع، التأكد من صلاحيات المستخدم (Authorization) داخل الـ Resolvers.
الخلاصة: هل يجب أن تتخلى عن REST؟
القصة وما فيها يا جماعة، GraphQL ليست “قاتل REST”. هي أداة أخرى قوية جدًا في صندوق أدوات المطور. تعلمها واستخدامها في المشاريع المناسبة كان من أفضل القرارات التقنية اللي أخذتها. لقد حولت علاقة العمل الفوضوية بين فريق الواجهة الخلفية والواجهة الأمامية إلى علاقة تعاون وتناغم.
هل يجب عليك تعلمها؟ بالتأكيد. هل يجب أن تستخدمها في كل مشروع؟ لا. افهم المشكلة التي تحاول حلها أولاً، ثم اختر الأداة المناسبة. لكن في المرة القادمة التي تجد فيها نفسك تغرق في بحر من الـ Endpoints، تذكر أن هناك طوق نجاة اسمه GraphQL قد يكون هو الحل الذي تبحث عنه. 🚀
وما تخافوا تجربوا إشي جديد، أفضل استثمار هو الاستثمار في نفسك وفي معرفتك. بالتوفيق يا أبطال! 🙏