Idempotency في Messaging APIs: كيف تمنع إرسال نفس الرسالة مرتين للعملاء؟
تخيّل أن أحد عملائك اشترى منتجًا من متجرك، فأرسل النظام رسالة SMS تؤكد الطلب. حدث Timeout قبل أن يحصل نظامك على استجابة واضحة من API، فقرر التطبيق إعادة الطلب تلقائيًا.
المشكلة؟
قد يكون الطلب الأول قد نجح بالفعل.
وهكذا يصل إلى العميل:
«تم تأكيد طلبك.»
ثم بعد ثوانٍ:
«تم تأكيد طلبك.»
بالنسبة للمطور، قد تبدو المشكلة بسيطة. لكن بالنسبة للعميل قد تعني وجود طلبين، أو عمليتين، أو خلل في النظام.
هنا يأتي دور Idempotency.

ما هي Idempotency؟
في تصميم APIs، تعني Idempotency أن تنفيذ العملية نفسها عدة مرات لا ينتج عنه تأثير إضافي بعد التنفيذ الأول.
بعبارة أبسط:
إذا طلب نظامك إرسال رسالة معينة مرة واحدة، ثم أعاد نفس الطلب بسبب مشكلة اتصال، يجب أن يستطيع النظام التمييز بين:
- طلب جديد يجب تنفيذه.
- Retry لطلب سابق لا يجب تنفيذه مرة أخرى.
Google Cloud تصف Idempotency بأنها خاصية تجعل تكرار العملية يعطي نفس التأثير النهائي لتنفيذها مرة واحدة، وهي مهمة خصوصًا في الأنظمة الموزعة التي تتعامل مع انقطاعات الشبكة والـtimeouts.
لماذا قد يتم إرسال نفس الرسالة مرتين؟
تكرار الرسائل لا يعني بالضرورة أن المطور استدعى API مرتين عمدًا.
هناك سيناريوهات شائعة جدًا.
1. Network Timeout
يرسل التطبيق طلب SMS.
تستقبل الخدمة الطلب وتبدأ معالجته، لكن الاتصال ينقطع قبل وصول Response إلى التطبيق.
التطبيق لا يعرف إن كان الإرسال تم أم لا.
إذا قرر تنفيذ Retry مباشرة، قد ينتج طلب إرسال جديد.
2. Automatic Retries
الكثير من الأنظمة تعيد تنفيذ الطلب تلقائيًا عند ظهور:
500 Internal Server Error
أو:
429 Too Many Requests
لكن إعادة الطلب ليست آمنة دائمًا.
فعلى سبيل المثال، توضح Twilio أن بعض عمليات POST مثل إرسال SMS ليست Idempotent بطبيعتها، وبالتالي قد ينتج عن Retry إرسال أكثر من رسالة.
3. Message Queues
إذا كنت تستخدم:
RabbitMQ
Kafka
Amazon SQS
أو Background Workers
فقد تتم إعادة معالجة Job عند عدم تسجيل acknowledgement بالشكل المتوقع.
ولهذا فإن مفهوم at-least-once delivery يعني عمليًا أن النظام يجب أن يكون مستعدًا لاحتمالية استقبال الحدث نفسه أكثر من مرة.
4. Concurrent Requests
أحيانًا تصل عمليتان متطابقتان في الوقت نفسه من Serverين أو Workers مختلفين.
إذا كان كلاهما يفحص قاعدة البيانات قبل تسجيل العملية، فقد يرى كلاهما أن الرسالة لم تُرسل بعد ثم يقومان بإرسالها.
وهنا تظهر مشكلة Race Condition.
ما هو Idempotency Key؟
الحل الشائع هو إعطاء كل عملية منطقية معرفًا فريدًا يسمى:
Idempotency Key
مثل:
550e8400-e29b-41d4-a716-446655440000
عند إرسال العملية لأول مرة، يسجل النظام المفتاح.
وعند وصول الطلب نفسه مرة أخرى بالمفتاح نفسه، يعرف النظام أنه Retry وليس عملية جديدة.
بدل إرسال الرسالة مرة ثانية، يمكن إعادة نتيجة العملية الأصلية.
AWS توصي باستخدام Idempotency Token فريد مع العمليات التي قد يعاد تنفيذها، مع الاحتفاظ بحالة العملية للتحقق من الطلبات المكررة.
مثال عملي: رسالة تأكيد طلب
لنفترض أن الطلب رقم:
ORDER-92831
يحتاج رسالة تأكيد.
بدل إنشاء معرف جديد في كل Retry، يمكنك ربط عملية الإرسال نفسها بمفتاح ثابت، مثل:
ORDER-92831-CONFIRMATION-SMS
تكون الآلية كالتالي:
- يصل طلب إرسال SMS.
- يفحص النظام Idempotency Key.
- إذا لم يكن موجودًا، يسجل العملية ويرسل الرسالة.
- يحفظ Message ID وحالة العملية.
- إذا وصل المفتاح نفسه مرة أخرى، لا ينشئ عملية إرسال جديدة.
النقطة المهمة هي أن كل عملية جديدة فعلًا يجب أن تحصل على Key جديد.
إذا أراد العميل مثلًا إعادة إرسال رسالة أخرى بعد ساعة بشكل مقصود، فهذا حدث جديد ويجب ألا يستخدم نفس المفتاح القديم.
كيف تطبق Idempotency في Messaging APIs؟
أنشئ Unique Identifier للعملية
يمكن استخدام UUID أو معرف منطقي مشتق من الحدث، بشرط أن يكون فريدًا للعملية نفسها ومستقرًا عند Retry.
خزّن حالة الطلب
احتفظ بمعلومات مثل:
idempotency_key
status
message_id
created_at
response
وقد تكون الحالة:
processing
sent
failed
افحص المفتاح قبل الإرسال
قبل تنفيذ طلب إرسال جديد، تحقق هل سبق تنفيذ هذا المفتاح.
إذا كانت العملية مكتملة، لا ترسل رسالة جديدة.
اجعل التسجيل والإرسال آمنين من Race Conditions
مجرد تنفيذ:
SELECT
ثم:
INSERT
قد لا يكون كافيًا عند وجود عدة Workers.
استخدم Unique Constraint أو Transaction أو آلية Lock مناسبة حتى لا يستطيع عاملان معالجة المفتاح نفسه في الوقت ذاته.
Idempotency أم Deduplication؟
المصطلحان مرتبطان لكنهما ليسا متطابقين.
Idempotency تركز على أن إعادة تنفيذ العملية نفسها لا تسبب Side Effect إضافيًا.
أما Deduplication فتركز على اكتشاف العناصر المكررة واستبعادها.
قد تستخدم Idempotency Key لتحقيق Deduplication، لكن يمكن أيضًا اكتشاف التكرار باستخدام Message ID أو Event ID.
مثلًا، بعض أنظمة الأحداث تطلب من المستقبِل حفظ Event ID والتأكد من عدم معالجته سابقًا. Twilio توصي بهذا الأسلوب عند التعامل مع أحداث قد تصل أكثر من مرة.
أفضل الممارسات
لا تجعل Retry عشوائيًا.
استخدم سياسة مثل Exponential Backoff بدل إعادة الطلب باستمرار.
ولا تولّد Idempotency Key جديدًا عند كل Retry؛ لأن ذلك يجعل كل محاولة تبدو وكأنها عملية جديدة.
كذلك لا تعتمد على نص الرسالة وحده لاكتشاف التكرار.
قد يكون من الطبيعي إرسال:
«رمز التحقق الخاص بك هو…»
للمستخدم نفسه أكثر من مرة في أوقات مختلفة.
الأفضل ربط المفتاح بالعملية التجارية نفسها، مثل Order ID أو Transaction ID أو Verification Session.
أخطاء شائعة
أحد أشهر الأخطاء هو افتراض أن عدم وصول Response يعني أن العملية فشلت.
قد تكون الخدمة نفذت العملية بينما ضاعت الاستجابة فقط.
خطأ آخر هو Retry لكل Status Code بالطريقة نفسها.
كما أن استخدام Timestamp وحده كمفتاح قد لا يكون تصميمًا مثاليًا، خصوصًا مع الطلبات المتزامنة. AWS تحذر من استخدام timestamps كـidempotency keys بسبب احتمالات عدم الدقة والتزامن.
متى تحتاج Idempotency؟
تزداد أهميتها عندما تكون رسائلك مرتبطة بعمليات آلية مثل:
- OTP.
- تأكيد الطلبات.
- تحديث حالة الشحن.
- إشعارات الدفع.
- حجوزات المواعيد.
- تنبيهات الأنظمة.
- الرسائل الناتجة من CRM أو ERP.
- Webhooks.
- Background Jobs.
كلما زاد حجم الرسائل واعتماد النظام على الأتمتة، أصبح التعامل مع retries والرسائل المكررة جزءًا أساسيًا من تصميم التكامل.
إذا كنت تطور متجرًا، تطبيقًا، CRM أو نظام ERP ويحتاج إلى إرسال رسائل آلية، يمكنك استكشاف SMS API من OurSMS والتعرف على خيارات التكامل المناسبة لبنية نظامك.
وللتكاملات الأكبر أو السيناريوهات التقنية الخاصة، تواصل مع فريق OurSMS لمناقشة المتطلبات قبل بدء التنفيذ.
الخلاصة
منع الرسائل المكررة لا يبدأ من شاشة الحملات، بل من تصميم Integration موثوق.
عند بناء Messaging API integration، تعامل دائمًا مع احتمالية أن يحدث Timeout أو Retry أو أن يصل Event أكثر من مرة.
وباستخدام Idempotency Keys، والتخزين الصحيح لحالة العمليات، وDeduplication، وسياسات Retry المناسبة، يمكنك بناء نظام إرسال أكثر ثباتًا وتقليل احتمالية وصول الرسالة نفسها للعميل أكثر من مرة.
FAQ
1. ما معنى Idempotency في API؟
هي تصميم يجعل إعادة الطلب نفسه لا تؤدي إلى تنفيذ أثر إضافي بعد نجاح التنفيذ الأول.
2. ما هو Idempotency Key؟
معرف فريد يمثل عملية واحدة ويُعاد استخدامه عند Retry لنفس العملية.
3. هل Retry يمكن أن يرسل SMS مرتين؟
نعم، إذا كانت عملية الإرسال غير Idempotent ولم يكن التطبيق يملك آلية لمنع Duplicate Requests.
4. ما الفرق بين Idempotency وDeduplication؟
Idempotency تمنع إعادة تنفيذ أثر العملية، بينما Deduplication تهدف إلى اكتشاف وحذف أو تجاهل العناصر المكررة.
5. هل يمكن استخدام UUID كـ Idempotency Key؟
نعم، UUID خيار شائع، بشرط الاحتفاظ بنفس UUID عند إعادة محاولة العملية نفسها.
6. كم يجب الاحتفاظ بـ Idempotency Key؟
يعتمد ذلك على طبيعة النظام وفترة الـretries المحتملة ومتطلبات العمل. لا توجد مدة واحدة مناسبة لكل الأنظمة.
7. هل Idempotency مهمة في OTP؟
نعم، خصوصًا لمنع إعادة إرسال OTP دون قصد نتيجة retries أو jobs مكررة، مع مراعاة أن طلب المستخدم لرمز جديد عمدًا يعتبر عملية جديدة.
8. هل POST Idempotent؟
ليس بالضرورة. POST غالبًا يحتاج منطقًا إضافيًا لجعله آمنًا عند Retry، بعكس بعض HTTP methods التي تكون Idempotent بطبيعتها.
