يا أهلاً وسهلاً فيكم يا جماعة الخير، معكم أخوكم أبو عمر.
خلوني أحكيلكم قصة صارت معي ومع فريقي قبل كم سنة، قصة علمتنا درس قاسي عن أهمية اختيار النمط المعماري الصحيح. كنا وقتها بنبني نظام لمعالجة الفيديوهات، نظام بستقبل فيديوهات من المستخدمين، بيعمل عليها شوية معالجة زي ضغط الفيديو، إضافة علامة مائية، وبعدين بخزنها. الشغلانة نفسها كانت تاخد وقت، من كم دقيقة لساعة أحياناً، حسب حجم الفيديو.
المشكلة ما كانت في المعالجة نفسها، المشكلة كانت في “كيف نعرف إنه الفيديو خلص معالجة؟”. في البداية، وبكل سذاجة المبتدئين (مع إنه ما كنا مبتدئين!)، لجأنا للحل الأسهل والأسرع: الاستعلام المستمر أو الـ Polling.
كنا عاملين API endpoint بسيطة، تطبيق الواجهة الأمامية (Front-end) كل 10 ثواني يبعت طلب يسأل السيرفر: “يا سيرفر، الفيديو رقم 123 خلص؟”، والسيرفر يرد عليه “لسا والله”، “لسا والله”، “لسا والله”… وهكذا دواليك. في البداية، مع كم مستخدم، الأمور كانت ماشية. بس لما زاد الضغط، بدأنا نشوف الكارثة. السيرفرات صارت “تصرخ” من كثرة الطلبات اللي ما إلها لزوم، استهلاك الموارد صار في السما، وتجربة المستخدم صارت في الحضيض لأنه أحياناً كان يشوف الفيديو جاهز بعد دقيقة كاملة من ما هو فعلاً خلص. وقتها واحد من الشباب صاح فينا: “يا جماعة شو هالحكي؟ احنا بنغرق السيرفر تبعنا بطلبات فاضية! لازم نلاقي حل!”.
ومن هنا، بدأت رحلتنا مع المنقذ: الـ Webhooks.
ما هو الاستعلام المستمر (Polling)؟ جحيم الانتظار الذي عانينا منه
قبل ما نحكي عن الحل، خلينا نفصّل أكتر في المشكلة. الـ Polling، أو الاستعلام الدوري، هو ببساطة إنك كـ “عميل” (Client) تضلك تسأل الـ “خادم” (Server) بشكل متكرر عن حالة شيء معين.
تخيل حالك سايق ومعك ابنك الصغير بالسيارة، وكل دقيقة بيسألك: “وصلنا يا بابا؟”. بعد خمس دقائق: “بابا، وصلنا؟”. بعد عشر دقائق… فهمت عليّ كيف؟ هذا بالضبط ما يفعله الـ Polling. العميل هو ابنك، والخادم هو أنت، والرحلة هي عملية المعالجة الطويلة.
كيف يعمل الـ Polling؟
الآلية بسيطة بشكل مخادع:
- العميل يرسل طلب HTTP (عادةً GET) إلى الخادم ليسأل عن حالة مورد معين. (مثال:
GET /api/videos/123/status). - الخادم يفحص الحالة ويرد. (مثال:
{"status": "processing"}). - العميل يستقبل الرد، ينتظر فترة زمنية محددة (مثلاً 10 ثواني).
- العميل يعود للخطوة 1 ويكرر العملية حتى يتغير الرد (مثال:
{"status": "completed"}).
كود بسيط باستخدام JavaScript لتوضيح الفكرة:
// هذا الكود يعمل في طرف العميل (المتصفح مثلاً)
const videoId = 123;
// دالة لفحص حالة الفيديو
async function checkVideoStatus() {
console.log("جاري فحص حالة الفيديو...");
const response = await fetch(`/api/videos/${videoId}/status`);
const data = await response.json();
if (data.status === 'completed') {
console.log("اكتملت المعالجة! يمكننا الآن عرض الفيديو.");
// أوقف التكرار
clearInterval(pollingInterval);
// قم بعرض الفيديو للمستخدم
showVideo();
} else {
console.log("لم تكتمل المعالجة بعد، سنعاود الفحص بعد 10 ثوانٍ.");
}
}
// ابدأ عملية الاستعلام المستمر كل 10 ثوانٍ
const pollingInterval = setInterval(checkVideoStatus, 10000);
مساوئ الـ Polling التي كادت أن تقتل مشروعنا
- إرهاق الخادم (Server Load): كل طلب استعلام يستهلك موارد: وحدة معالجة مركزية (CPU)، ذاكرة (Memory)، واتصال شبكة. تخيل 1000 مستخدم يفعلون هذا كل 10 ثوانٍ! إنها 100 طلب في الثانية لا تفعل شيئًا سوى قول “لا جديد”.
- تأخير لا مفر منه (Latency): البيانات ليست فورية. إذا انتهت المعالجة في الثانية الأولى بعد آخر استعلام، فسيظل العميل ينتظر 9 ثوانٍ أخرى ليعرف ذلك. هذا التأخير هو “وقت ضائع” تمامًا.
- هدر الموارد والبيانات (Wasted Resources): الأغلبية الساحقة من الطلبات (99.9% في حالتنا) كانت تعود بنفس الإجابة: “لم يكتمل بعد”. هذا هدر كبير في عرض النطاق الترددي (Bandwidth) ودورات المعالجة.
- صعوبة التوسع (Scalability): مع نمو عدد المستخدمين، ينمو عدد طلبات الـ Polling بشكل خطي، مما يجعل النظام ينهار تحت وزنه.
الحل السحري: نمط الـ Webhooks (الخطّافات الرقمية)
بعد ما “انزنقنا” وعرفنا إنه الـ Polling مش راح يكمّل معنا، انتقلنا للبديل الأنيق والفعال: الـ Webhooks. الاسم ممكن يكون غريب شوي، “خطّاف الويب”، بس الفكرة عبقرية.
الـ Webhook هو عكس الـ API التقليدي. فبدلاً من أن يقوم العميل بسؤال الخادم، يقوم الخادم بإخبار العميل عند وقوع حدث معين. إنها معمارية قائمة على الأحداث (Event-driven).
فكر فيها كالتالي: الـ Polling مثل أن تتصل بمطعم البيتزا كل 5 دقائق لتسأل إن كانت طلبيتك جاهزة. أما الـ Webhook، فهو أن يعطيك المطعم “جهاز بيجر”، وعندما تجهز البيتزا، يهتز الجهاز في يدك. أنت لا تفعل شيئًا، فقط تنتظر الإشعار.
آلية عمل الـ Webhooks خطوة بخطوة
العملية أكثر منطقية وكفاءة:
- التسجيل (Registration): في البداية، يقوم نظامنا (العميل) بتزويد نظام معالجة الفيديو (الخادم) بعنوان URL خاص بنا. هذا الـ URL يسمى “Webhook Endpoint”. نقول له: “يا نظام المعالجة، عند انتهاء معالجة أي فيديو، من فضلك أرسل لي إشعارًا على هذا الرابط:
https://my-app.com/api/webhooks/video-processed“. - الحدث (The Event): يقوم الخادم بمعالجة الفيديو كالمعتاد. وعندما تنتهي العملية بنجاح (أو تفشل)، يحدث “الحدث”.
- الإشعار (The Notification): فور وقوع الحدث، يقوم الخادم بتكوين طلب HTTP POST، يضع فيه كل البيانات المهمة (مثل ID الفيديو، رابط الملف الجديد، الحالة، إلخ) في جسم الطلب (Payload)، ويرسله إلى الـ URL الذي سجلناه في الخطوة الأولى.
- الاستجابة (The Response): يستقبل الخادم الخاص بنا هذا الطلب، يقرأ البيانات، ويقوم بما يلزم (مثلاً، تحديث قاعدة البيانات، إرسال إيميل للمستخدم). الأهم من ذلك، يجب أن يرد فورًا على طلب الـ Webhook برمز حالة
200 OKليعلم المرسل أننا استلمنا الإشعار بنجاح.
نصائح من مطبخ أبو عمر: كيف تطبق الـ Webhooks كالمحترفين؟
تطبيق الـ Webhooks يبدو سهلاً، ولكنه يحتوي على بعض “المطبات” التي وقعنا فيها وتعلمنا منها. خذوا مني هذه النصائح العملية من الآخر:
1. أمّن الـ Endpoint الخاص بك!
الـ Webhook endpoint الخاص بك هو عنوان عام على الإنترنت. أي شخص يعرفه يمكنه إرسال طلبات إليه. هذا يعني أن أي شخص يمكنه إرسال بيانات مزيفة إلى نظامك! الحل هو التحقق من التوقيع (Signature Verification).
كيف يعمل؟ الخدمة التي ترسل الـ Webhook (مثل Stripe, GitHub, أو خدمتك الخاصة) تشاركك “سرًا” (Secret Key). عند إرسال كل Webhook، تقوم بحساب توقيع (Signature) للبيانات باستخدام هذا السر، وترسله كـ Header في الطلب (مثلاً X-Signature). مهمتك أنت هي أن تعيد حساب نفس التوقيع بنفس الطريقة باستخدام السر الذي لديك، وتقارنه بالتوقيع المرسل. إذا تطابقا، فالطلب أصلي. إذا لم يتطابقا، تجاهل الطلب فورًا.
2. لا تجعلهم ينتظرون: استجب بسرعة!
أسوأ شيء يمكن أن تفعله هو إجراء عمليات معقدة وطويلة داخل الـ Webhook handler نفسه. معظم الخدمات التي ترسل Webhooks لديها مهلة قصيرة (Timeout)، وإذا لم ترد بسرعة (خلال 2-5 ثوانٍ)، ستعتبر أن طلبها فشل وقد تحاول إعادة إرساله، مما يسبب فوضى.
الحل الاحترافي: استخدم طابور رسائل (Message Queue) مثل RabbitMQ, SQS, Redis, أو حتى جدول بسيط في قاعدة البيانات.
السيناريو الصحيح:
- الـ Webhook Endpoint يستقبل الطلب.
- يتحقق من التوقيع (سريع جدًا).
- يضع جسم الطلب (Payload) كما هو في طابور الرسائل.
- يرد فورًا بـ
200 OK. - بشكل منفصل وغير متزامن، يوجد لديك “عامل” (Worker) يراقب الطابور، وعندما يرى رسالة جديدة، يسحبها ويقوم بكل العمليات الثقيلة (تحديث قاعدة البيانات، إرسال إيميلات، استدعاء خدمات أخرى…).
بهذه الطريقة، يكون الـ Endpoint الخاص بك سريعًا جدًا وموثوقًا، وتفصل بين عملية “الاستلام” وعملية “المعالجة”.
3. كن مستعداً للفشل (Handling Failures)
ماذا لو كان خادمك معطلاً لحظة إرسال الـ Webhook؟ ماذا لو حدث خطأ أثناء المعالجة؟
- آليات إعادة المحاولة (Retry Mechanisms): تأكد من أن الخدمة المرسلة تدعم إعادة المحاولة التلقائية (معظم الخدمات المحترمة تفعل ذلك)، غالبًا بآلية تسمى “Exponential Backoff” (تزيد فترة الانتظار بين كل محاولة فاشلة).
- التسجيل والمراقبة (Logging & Monitoring): قم بتسجيل (Log) كل طلب Webhook يصلك (بعد التحقق من صحته). هذا يساعدك على تتبع المشاكل ومعرفة ما إذا فاتك شيء ما.
- التعامل مع التكرار (Idempotency): بسبب آليات إعادة المحاولة، قد يصلك نفس الـ Webhook أكثر من مرة. يجب أن يكون نظامك مصممًا للتعامل مع هذا الموقف بأمان. على سبيل المثال، إذا كان الـ Webhook هو “اخصم 10 دولارات من حساب المستخدم”، يجب أن تتأكد من أن الخصم يحدث مرة واحدة فقط، حتى لو وصلك الطلب 5 مرات. يمكنك تحقيق ذلك عن طريق حفظ ID فريد لكل حدث تتم معالجته.
مثال عملي: بناء Webhook Endpoint بسيط باستخدام Node.js و Express
هذا مثال بسيط يوضح كيفية بناء نقطة نهاية (Endpoint) تستقبل Webhooks، تتحقق من التوقيع (بشكل مبسط)، وتستخدم طابورًا وهميًا للمعالجة غير المتزامنة.
const express = require('express');
const crypto = require('crypto');
const app = express();
// استخدم هذا Middleware لقراءة جسم الطلب بصيغته الخام (raw)
// هذا ضروري للتحقق من التوقيع بشكل صحيح
app.use(express.json({
verify: (req, res, buf) => {
req.rawBody = buf.toString();
}
}));
const WEBHOOK_SECRET = 'هذا_هو_السر_المشترك_بيني_وبين_المرسل';
// طابور وهمي (في التطبيقات الحقيقية، استخدم RabbitMQ, SQS, etc.)
const jobQueue = [];
// دالة العامل الوهمي التي تعالج المهام
function processQueue() {
if (jobQueue.length > 0) {
const job = jobQueue.shift(); // اسحب أول مهمة
console.log(`[Worker] جاري معالجة المهمة: ${JSON.stringify(job)}`);
// ... هنا تضع منطق المعالجة الثقيل ...
// مثلاً: تحديث قاعدة البيانات، إرسال إيميل، إلخ.
}
}
// تشغيل العامل كل 5 ثوانٍ لفحص الطابور
setInterval(processQueue, 5000);
// هذا هو الـ Webhook Endpoint
app.post('/api/webhooks/video-processed', (req, res) => {
// 1. التحقق من التوقيع
const receivedSignature = req.headers['x-signature'];
const expectedSignature = crypto
.createHmac('sha256', WEBHOOK_SECRET)
.update(req.rawBody)
.digest('hex');
if (receivedSignature !== expectedSignature) {
console.error('التوقيع غير صالح! طلب مشبوه.');
return res.status(401).send('Invalid Signature');
}
// 2. التوقيع صالح، ضع المهمة في الطابور
const payload = req.body;
console.log(`[Webhook] تم استلام Webhook صالح. ID الفيديو: ${payload.videoId}`);
jobQueue.push(payload);
// 3. أرسل استجابة فورية
res.status(200).send('Webhook Received');
});
const PORT = 3000;
app.listen(PORT, () => {
console.log(`خادم الـ Webhook يعمل على المنفذ ${PORT}`);
});
الخلاصة: متى تختار هذا وذاك؟
بعد كل هذه القصة، السؤال هو ليس “أيهما أفضل؟” بل “أيهما أنسب للموقف؟”.
- استخدم الـ Polling عندما:
- الخدمة التي تتعامل معها لا تدعم الـ Webhooks (وهذا هو السبب الأكثر شيوعًا).
- أنت تبني نموذجًا أوليًا سريعًا ولا تهتم بالكفاءة الآن.
- البيانات ليست حساسة للوقت، ولا بأس من وجود تأخير.
- أنت تعلم بالضبط متى تتغير البيانات (مثلاً، تقرير يتم إنشاؤه كل ساعة على رأس الساعة).
- استخدم الـ Webhooks عندما:
- تحتاج إلى تحديثات فورية أو شبه فورية.
- الكفاءة واستهلاك الموارد والتوسع هي أولوياتك.
- تريد بناء أنظمة حديثة تعتمد على الأحداث (Event-Driven Architecture).
- الخدمة التي تتعامل معها تدعمها (وهو ما تفعله معظم الخدمات الحديثة).
في النهاية، الانتقال من Polling إلى Webhooks كان نقلة نوعية في مشروعنا. لقد حرر خوادمنا، وجعل تطبيقنا أسرع وأكثر استجابة، وعلمنا درسًا قيمًا في تصميم النظم. لا تخافوا من تغيير معمارياتكم القديمة إذا كانت تسبب لكم الصداع، فالنمط الصحيح يمكن أن يوفر عليكم ليالٍ طويلة من المراقبة وإصلاح المشاكل.
اختاروا أدواتكم بحكمة يا جماعة… ودمتم مبرمجين مبدعين! 👍