مقدمة
البوت أو الأتمتة الصغيرة تبدأ عادة بسكربت يعمل على جهازك، ثم تكتشف المشاكل في الإنتاج: رسائل تصل مرتين، وطلبات تنتهي مهلتها، وخدمة خارجية تتعطل، وحظر بسبب كثرة الطلبات. هذا الدرس يجمع الأنماط التي تجعل الأتمتة موثوقة.
أهداف التعلم
- الاختيار بين الاستطلاع (Polling) والـ Webhooks.
- التحقق من أن الطلب قادم فعلاً من الخدمة.
- الرد السريع ونقل العمل الثقيل إلى طابور.
- جعل المعالجة عديمة التكرار (Idempotent) مع إعادة محاولة آمنة.
1. Polling أم Webhook؟
- Polling: بوتك يسأل الخدمة كل فترة «هل من جديد؟» (مثل
getUpdatesفي تيليجرام). بسيط ومناسب للتطوير. - Webhook: الخدمة ترسل طلب HTTP إلى عنوانك عند كل حدث. أسرع وأكفأ في الإنتاج، لكنه يحتاج عنوان HTTPS عاماً وحماية.
2. تحقق من مصدر الطلب
أي شخص يعرف عنوان الـ Webhook يمكنه إرسال طلبات مزيفة. تيليجرام يسمح بتحديد رمز سري يُرسل في ترويسة مع كل طلب:
// عند التسجيل: setWebhook مع secret_token
// عند الاستقبال:
import { timingSafeEqual } from 'node:crypto';
function validSecret(received: string | null, expected: string) {
if (!received) return false;
const a = Buffer.from(received), b = Buffer.from(expected);
return a.length === b.length && timingSafeEqual(a, b);
}
app.post('/telegram/webhook', (req, res) => {
if (!validSecret(req.get('X-Telegram-Bot-Api-Secret-Token'), process.env.TG_WEBHOOK_SECRET!)) {
return res.status(401).end();
}
// ...
});
خدمات أخرى (GitHub، Stripe، مزودو البريد) توقّع جسم الطلب بـ HMAC؛ احسب التوقيع على الجسم الخام وقارنه بمقارنة ثابتة الزمن.
3. رد بسرعة، واعمل لاحقاً
الخدمات تنتظر ردك لثوانٍ معدودة ثم تعتبر الطلب فاشلاً وتعيد إرساله. لا تستدعِ نموذج ذكاء اصطناعي أو API بطيئاً داخل معالج الـ Webhook:
app.post('/telegram/webhook', async (req, res) => {
// ... التحقق من السر
await db.job.create({ data: { kind: 'tg_update', key: `tg:${req.body.update_id}`, payload: req.body } })
.catch(ignoreUniqueViolation); // التكرار مرفوض بالقيد الفريد على key
res.status(200).end(); // رد فوري
});
ثم عامل منفصل يعالج المهام من الطابور.
4. عدم التكرار (Idempotency)
الخدمات تعيد الإرسال عند أي شك، فستستقبل الحدث نفسه أكثر من مرة. اجعل لكل حدث مفتاحاً فريداً (update_id في تيليجرام، معرّف الحدث في غيرها) وقيداً فريداً في القاعدة، فتُعالج كل رسالة مرة واحدة مهما تكررت.
والأمر نفسه عند إرسالك أنت: إن أعدت محاولة طلب إلى خدمة خارجية، استخدم مفتاح عدم تكرار إن كانت تدعمه، حتى لا تُرسل الرسالة أو الدفعة مرتين.
5. العامل: سحب آمن وإعادة محاولة
-- يأخذ دفعة مهام لم يأخذها عامل آخر (آمن مع عدة عمال)
UPDATE jobs SET locked_at = now(), attempts = attempts + 1
WHERE id IN (
SELECT id FROM jobs
WHERE done_at IS NULL AND run_after <= now()
ORDER BY created_at
LIMIT 20
FOR UPDATE SKIP LOCKED
)
RETURNING *;
عند الفشل: أعد جدولة المهمة بتأخير متزايد (run_after = now() + 2^attempts دقيقة)، وبعد عدد محاولات معين انقلها إلى «الرسائل الميتة» لمراجعتها يدوياً بدل تكرارها للأبد.
6. احترم الحدود
لكل منصة حدود إرسال (تيليجرام مثلاً يحد عدد الرسائل في الثانية لكل محادثة وعموماً). أرسل عبر طابور بمعدل محدود، وعند رد 429 اقرأ مدة الانتظار المقترحة وانتظرها.
تمرين عملي
- أنشئ بوت تيليجرام بـ BotFather، وسجّل Webhook مع
secret_token. - احفظ كل تحديث في جدول بقيد فريد على
update_id، ورد 200 فوراً. - اكتب عاملاً يعالج المهام ويرد على المستخدم، وأوقفه عمداً أثناء المعالجة ثم أعد تشغيله وتأكد أن لا رسالة تُفقد أو تتكرر.
أخطاء شائعة
- معالجة ثقيلة داخل معالج الـ Webhook فتنتهي المهلة وتتكرر الطلبات.
- عدم التحقق من المصدر.
- غياب مفتاح عدم التكرار، فتُنفذ العملية مرتين.
- إعادة محاولة لا نهائية دون حد أو «رسائل ميتة».
- وضع رمز البوت في الكود أو المستودع.
الخلاصة
تحقق من المصدر، رد فوراً وانقل العمل لطابور، اجعل كل حدث يُعالج مرة واحدة بمفتاح فريد، وأعد المحاولة بتأخير متزايد وحد أقصى. بهذه الأنماط تصمد الأتمتة أمام أعطال الشبكة والخدمات.
مصادر مقترحة
- توثيق Telegram Bot API (setWebhook وsecret_token).
- مقال Stripe عن «Idempotent requests».
- توثيق PostgreSQL عن
SELECT ... FOR UPDATE SKIP LOCKED.