احفظ الحالة وامنع تكرار الأثر

اكتب حالات العمل وانتقالاتها

الحالة تجيب عن سؤال أين وصل العمل الآن، بينما السجل يحتفظ بما حدث قبله. لو عندك حقل واحد اسمه done، لن تميز انتظار موافقة عن تعطل مؤقت.

هنستخدم received ثم validated ثم pending_approval ثم approved ثم executing ثم completed. الحالات needs_info و failed و unknown توضح توقفًا أو التباسًا، وكل انتقال له شرط.

approved لا ينتقل إلى completed لمجرد ضغط زر؛ يحتاج تنفيذًا وإيصالًا مناسبًا. و unknown لا يتحول إلى failed من غير دليل لأن الأثر ربما حصل.

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

الخريطة الصغيرة تسمح لك تشرح كل حالة للطالب وتحدد الخطوة التالية. بعد ذلك نضيف حفظًا متينًا حتى لا تضيع الحالة عند إعادة تشغيل العملية.

مثال محلول
0017 بياناته صحيحة؛ الموافقة لم تحدث؛ لم يوجد إيصال تنفيذ.

الطلب
حدد الحالة المسموحة والانتقالات المرفوضة.

المخرج
الحالة pending_approval. لا executing ولا completed. بعد موافقة صحيحة→approved؛ وبعد بدء التنفيذ→executing؛ الإتمام يحتاج إيصالًا.

لماذا؟
الحالة مرتبطة بالدليل المتاح.
الموافقة تسبق الأثر.
الإيصال يثبت النتيجة وفق العقد.

اختار مفتاحًا يعرّف الأثر

منع التكرار يعتمد على تعريف نفس العملية. رقم التشغيل يتغير مع كل محاولة، لذلك استخدامه وحده كمفتاح يجعل كل إعادة تبدو عملًا جديدًا.

في مدار الأثر هو إنشاء مسودة للطلب 0017 بنسخة محتوى 3 داخل الشركة T1. مفتاح مثل T1:0017:create_draft:v3 يربط هذه الحدود في هوية واحدة.

لو تغير المحتوى إلى v4، لا نعيد استخدام نفس المفتاح مع جسم مختلف بصمت. نعالجها كنسخة جديدة تحتاج فحصًا وموافقة مناسبة قبل أي أثر.

المفتاح لا يضمن شيئًا بمجرد كتابته في Header. الطرف المنفذ يحتاج دعمًا موثقًا يحفظ النتيجة ويمنع تكرار نفس الأثر، أو ننفذ آلية بديلة موثوقة.

بنختبر نفس المفتاح مع نفس الجسم، ثم نفس المفتاح مع جسم مختلف. المتوقع رجوع نفس النتيجة للأول ورفض التعارض للثاني حسب عقدنا.

مثال محلول
T1؛ request0017؛ action=create_draft؛ revision3. محاولتان متطابقتان.

الطلب
اقترح مفتاحًا ثابتًا وحدد السلوك عند تغيير الجسم دون تغيير النسخة.

المخرج
key=T1:0017:create_draft:v3. المحاولتان تشتركان في نتيجة واحدة. نفس key مع body مختلف→conflict، لا تنفيذ ثانٍ.

لماذا؟
المفتاح يصف الأثر لا المحاولة.
الشركة جزء من العزل.
تعارض الجسم لا يمر بصمت.

احجز العملية ذريًا عند التزامن

فحص وجود العملية ثم إنشاؤها في خطوتين منفصلتين يترك نافذة سباق. عاملان قد يقرآن غير موجود ثم ينفذان نفس الأثر.

الحل يحتاج ضمانًا من مخزن الحالة، مثل قيد فريد على مفتاح العملية وحجز ذري. العامل الذي نجح في الحجز يكمل؛ الثاني يقرأ الحالة المسجلة بدل إنشاء عملية أخرى.

لا نعتمد على شرط في الواجهة ولا على Prompt يقول لا تكرر. منع السباق مسؤولية طبقة التنفيذ والتخزين.

الحجز وحده لا يحل انقطاع العامل بعد الكتابة الخارجية؛ لهذا يحتفظ السجل بحالة ومرجع وزمن، ونضيف استعلامًا وتصالحًا مع الطرف الخارجي.

المثال يركز على سباق الوصول. في تنفيذ حقيقي يجب اختبار عاملين متزامنين، وليس ضغط زر مرتين على التوالي فقط.

مثال محلول
العامل A والعامل B يحاولان حجز نفس key في نفس اللحظة؛ المخزن يفرض unique key.

الطلب
حدد الفائز وسلوك العامل الآخر دون إنشاء ثانٍ.

المخرج
A يحجز العملية. B يحصل على تعارض القيد ثم يقرأ حالة A. لا أثر جديد من B؛ إذا الحالة قيد التنفيذ يعرض انتظارًا.

لماذا؟
الحجز واحد بفضل ضمان المخزن.
العامل الخاسر لا يتجاوز القيد.
المراقبة بديل عن مضاعفة العمل.

خطط لانقطاع بعد الأثر وقبل التسجيل

أصعب لحظة أن الطرف الخارجي يكتب المسودة، ثم العامل يتوقف قبل حفظ الإيصال محليًا. السجل المحلي يقول executing بينما الأثر موجود فعلًا.

لو بدأت من جديد وأنشأت مسودة بلا استعلام، سيتكرر الأثر. نستخدم مفتاح العملية لدى الطرف الخارجي إن كان يدعمه، أو مرجعًا قابلًا للاستعلام.

في المثال وجدنا D17 مرتبطًا بالمفتاح نفسه، فنحفظ الإيصال ونغلق العملية completed. كلمة completed هنا تخص إنشاء المسودة، لا إنجاز الصيانة.

لو لا توجد وسيلة موثوقة للتحقق، ننقل الحالة إلى unknown ومراجعة بشرية. لا نقدم ضمانًا عامًا بالتنفيذ مرة واحدة عبر أي نظامين.

اختبار الانقطاع يجب أن يقع في هذه النقطة تحديدًا. اختبار قطع الاتصال قبل الكتابة فقط لا يغطي خطر أثر موجود وإيصال مفقود.

مثال محلول
خارجيًا D17 موجود لنفس key؛ محليًا executing بعد تعطل العامل قبل حفظ الإيصال.

الطلب
اكتب خطة استعادة الحالة دون إنشاء جديد.

المخرج
استعلم بال key→D17. احفظ المرجع محليًا وسجل استعادة بعد انقطاع. completed لإنشاء المسودة فقط؛ لا إعادة POST.

لماذا؟
استندنا لأثر موجود.
حدود completed محددة.
الانقطاع لم يضاعف التنفيذ.

اربط الإعادة بالنسخة وسبب الفشل

إعادة المحاولة الصحيحة تعيد نفس العمل عند مشكلة مؤقتة. أما تغيير الفرع أو المبلغ فهو تعديل محتوى يحتاج نسخة جديدة، وليس Retry لنفس الموافقة.

مدار وافقت على مسودة v3 للمعادي، ثم المستخدم غيرها لمدينة نصر. لا يجوز استخدام موافقة v3 لتنفيذ v4 حتى لو رقم الطلب لم يتغير.

نسجل revision وملخص المحتوى أو بصمته مع الموافقة. عند التنفيذ نقارن النسخة الحالية بالمعتمدة؛ لو اختلفت نعيدها للمراجعة.

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

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

مثال محلول
approved_revision=3؛ current_revision=4؛ الفرع تغير من المعادي لمدينة نصر.

الطلب
حدد حالة التنفيذ وما يلزم لاستكمالها.

المخرج
التنفيذ موقوف بسبب revision_mismatch. نعرض v4 للمراجع؛ موافقة v3 لا تنتقل تلقائيًا. نحتفظ بسجل النسختين.

لماذا؟
الفرع جزء من المحتوى المعتمد.
منع التكرار لا يمنح موافقة جديدة.
السجل يفسر سبب التوقف.

اختبر التكرار والتزامن والانقطاع معًا

حزمة اختبار الحالة تجمع سيناريوهات لا تظهر في التجربة السعيدة:حدث مكرر، وعاملان معًا، وانقطاع بعد الأثر، وموافقة على نسخة قديمة.

لكل سيناريو نعد الآثار الخارجية ونراجع السجل. عدد المحاولات قد يزيد، لكن الأثر المقصود لكل key يبقى واحدًا حين يدعم العقد ذلك.

لو نجح التكرار المتتابع وفشل التزامن، فالحل غير مكتمل. ولو نجح الحجز وفشل الاسترجاع بعد الانقطاع، ما زال خطر التكرار قائمًا.

نتيجة الاختبار تسجل ضمان البيئة المحددة فقط. لا نعممها على موصل آخر لا يدعم المفاتيح أو الاستعلام بنفس الطريقة.

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

مثال محلول
S1 حدث مكرر؛ S2 عاملان؛ S3 أثر D17 مع إيصال ضائع؛ S4 نسخة معتمدة 3 وحالية 4.

الطلب
اكتب مرجع نتائج وعدد الأثر المتوقع.

المخرج
S1 أثر واحد؛ S2 حجز واحد؛ S3 ربط D17 دون أثر جديد؛ S4 صفر أثر بالنسخة الجديدة قبل إعادة الموافقة. هذه توقعات اختبار لعقد محدد.

لماذا؟
التكرار والتزامن مختلفان.
الاسترجاع يعتمد على مرجع خارجي.
النسخة الجديدة لا تستعير اعتماد القديمة.