11 يونيو 2025·7 دقيقة قراءة

القفل التفاؤلي لأدوات الإدارة: منع الكتابات الصامتة

تعلم القفل التفاؤلي لأدوات الإدارة باستخدام أعمدة الإصدار وفحوص `updated_at`، مع أنماط واجهة بسيطة لحل تعارضات التحرير ومنع الكتابات الصامتة.

القفل التفاؤلي لأدوات الإدارة: منع الكتابات الصامتة

المشكلة: الكتابات الصامتة عندما يحرر الكثيرون\n\nتحدث "الكتابة الصامتة" عندما يفتح شخصان نفس السجل، يجري كل منهما تغييرات، ويكون لمن يضغط حفظ أخيرًا الأثر. تختفي تغييرات الأول بلا تحذير وغالبًا بلا طريقة سهلة للاسترجاع.\n\nفي لوحة إدارة مزدحمة، يمكن أن يحدث هذا طوال اليوم بدون أن ينتبه أحد. يحتفظ الناس بعلامات تبويب متعددة، ينتقلون بين التذاكر، ويعودون إلى نموذج جلس فيه لمدة 20 دقيقة. عندما يحفظون أخيرًا، لا يكونون يعدّلون أحدث نسخة من السجل—هم يكتبون فوقها.\n\nيظهر هذا أكثر في أدوات المكتب الخلفية منه في التطبيقات العامة لأن العمل تعاوني ومبني على سجلات. الفرق الداخلية تعدّل نفس العملاء والطلبات والمنتجات والطلبات مرارًا، غالبًا على دفعات قصيرة. التطبيقات العامة تكون غالبًا "مستخدم واحد يعدّل بياناته"، بينما أدوات الإدارة تكون "عدة مستخدمين يعدّلون عناصر مشتركة".\n\nالضرر نادرًا ما يكون دراميًا في اللحظة، لكنه يتراكم بسرعة:\n\n- يتغير سعر منتج إلى قيمة قديمة بعد تحديث عرض ترويجي.\n- تختفي ملاحظة داخلية لوكيل الدعم، فيكرر الوكيل التالي نفس خطوات التحري.\n- يعود حالة طلب إلى الخلف (مثلًا من "Shipped" إلى "Packed") مما يسبب متابعة خاطئة.\n\nالكتابات الصامتة مؤلمة لأن الجميع يظن أن النظام حفظ بشكل صحيح. لا يوجد لحظة واضحة "حدث خطأ"، بل ارتباك لاحق عندما تظهر تقارير غير متوقعة أو يسأل زميل "من غيّر هذا؟"\n\nالتعارضات مثل هذه طبيعية. إنها علامة أن الأداة مشتركة ومفيدة، لا علامة على أن فريقك يفعل خطأ. الهدف ليس منع شخصين من التحرير، بل اكتشاف تغير السجل أثناء التحرير ومعالجة هذه اللحظة بأمان.\n\nإذا تبنيت أداة داخلية على منصة بدون كود مثل AppMaster، فابدأ التخطيط لهذا مبكرًا. أدوات الإدارة تنمو سريعًا، ومتى ما اعتمدت عليها الفرق، يصبح فقدان البيانات "من وقت لآخر" مصدر تآكل في الثقة.\n\n## القفل التفاؤلي بلغة بسيطة\n\nعندما يفتح شخصان نفس السجل ويضغط كلاهما حفظ، لديك حالة تزامن. بدأ كل منهما من لقطة أقدم، لكن لا يمكن أن يكون أحدثهما إلا واحدًا عند الحفظ.\n\nبدون حماية، آخر حفظ يفوز. هكذا تحدث الكتابات الصامتة: الحفظ الثاني يستبدل تغييرات الأول بصمت.\n\nالقفل التفاؤلي قاعدة بسيطة: "سأحفظ تغييري فقط إذا بقي السجل في نفس الحالة التي بدأتها." إذا تغيّر السجل في هذه الأثناء، يتم رفض الحفظ ويُعرض للمستخدم تعارض.\n\nهذا يختلف عن القفل التشاؤمي (pessimistic locking) الذي أقرب إلى "أنا أتحرر هذا الآن، فلا أحد يستطيع التحرير." القفل التشاؤمي عادةً يعني أقفالًا صارمة، مهلات، وقيودًا على المستخدمين. قد يكون مفيدًا في حالات نادرة (مثل نقل الأموال بين حسابين)، لكنه غالبًا ما يكون محبطًا في أدوات الإدارة المزدحمة حيث تحدث تعديلات صغيرة طوال اليوم.\n\nالقفل التفاؤلي عادةً الأفضل افتراضيًا لأنه يحافظ على تدفق العمل. يمكن للناس التحرير بالتوازي، ويتدخل النظام فقط عند حدوث تصادم حقيقي.\n\nيناسب هذا الأسلوب عندما:\n\n- التعارضات ممكنة لكنها ليست مستمرة.\n- التعديلات سريعة (بضعة حقول أو نموذج قصير).\n- منع الآخرين سيبطئ الفريق.\n- يمكنك عرض رسالة واضحة "شخص ما حدّث هذا".\n- يمكن لواجهة برمجة التطبيقات التحقق من إصدار (أو طابع زمني) مع كل تحديث.\n\nما يمنعه هو مشكلة "الاستبدال الصامت". بدلًا من فقدان البيانات، تحصل على توقف واضح: "لقد تغيّر هذا السجل منذ فتحك له."\n\nومع ذلك، هناك حدود يجب مراعاتها. لن يمنع القفل التفاؤلي شخصين من اتخاذ قرارات مختلفة وصالحة اعتمادًا على نفس معلومات قديمة، ولا يدمج التغييرات تلقائيًا. وإذا تخلّيت عن الفحص على الجانب الخادمي، فلن تكون قد حليت المشكلة فعليًا.\n\nقيود شائعة:\n\n- لن يحلّ التعارضات تلقائيًا (لا يزال عليك اختيارًا).\n- لن يساعد إذا حرّر المستخدمون أوفلاين ثم مزجوا التغييرات لاحقًا بدون فحوص.\n- لن يصلح الأذونات الخاطئة (يمكن لشخص ما أن يظل يحرر ما لا ينبغي له).\n- لن يكتشف التعارضات إذا قمت بالفحص فقط على العميل.\n\nعمليًا، القفل التفاؤلي هو قيمة إضافية تُحمَل مع التعديل، بالإضافة إلى قاعدة خادم "التحديث فقط إذا تطابق". في AppMaster، عادةً يكون هذا الفحص داخل منطق الأعمال حيث تُنفّذ التحديثات.\n\n## نهجان شائعان: عمود الإصدار مقابل updated_at\n\nلكي تكشف أن السجل تغيّر أثناء تحرير المستخدم، تختار عادةً إشارة واحدة من اثنتين: رقم إصدار أو طابع زمني updated_at.\n\n### النهج 1: عمود الإصدار (عدد صحيح متزايد)\n\nأضف حقل version (عادة عدد صحيح). عند تحميل نموذج التحرير، اقرأ version الحالي. عند الحفظ، أعد إرسال نفس القيمة.\n\nينجح التحديث فقط إذا لا يزال الإصدار المخزن يطابق ما بدأ به المستخدم. إذا تطابق، حدّث السجل وزِد version بواحد. إذا لم يتطابق، أعد تعارضًا بدلًا من الكتابة فوق.\n\nهذا سهل الفهم: الإصدار 12 يعني "هذا هو التغيير الثاني عشر." كما أنه يتفادى حالات الحواف المتعلقة بالزمن.\n\n### النهج 2: updated_at (مقارنة الطابع الزمني)\n\nمعظم الجداول تحتوي بالفعل على حقل updated_at. الفكرة نفسها: اقرأ updated_at عند فتح النموذج، ثم أرسله مع الحفظ. الخادم يحدث فقط إذا لم يتغير updated_at.\n\nيمكن أن يعمل جيدًا، لكن الطوابع الزمنية لها نقاط ضعف. قواعد بيانات مختلفة تخزن بدقة مختلفة. بعضها يقرب إلى الثواني، مما قد يفوت تعديلات سريعة. إذا كتبت عدة أنظمة إلى نفس قاعدة البيانات، قد يسبب انحراف الساعة ومعالجة المناطق الزمنية حوافًا مربكة.\n\nطريقة مقارنة بسيطة:\n\n- عمود الإصدار: سلوك أوضح، محمول عبر قواعد البيانات، بدون مشاكل ساعة.\n- updated_at: غالبًا "مجانًا" لأنه موجود بالفعل، لكن الدقة والتعامل مع الساعة قد يسببا مشاكل.\n\nلفِرَق كثيرة، عمود الإصدار هو الإشارة الأفضل كقاعدة. إنه صريح، متوقع، وسهل الرجوع إليه في السجلات وتذاكر الدعم.\n\nفي AppMaster، يعني هذا عادةً إضافة حقل version عدد صحيح في Data Designer والتأكد من أن منطق التحديث يتحقق منه قبل الحفظ. يمكنك الاحتفاظ بـ updated_at للتدقيق، لكن دعه يكون رقم الإصدار الذي يقرر ما إذا كان التعديل آمنًا للتطبيق.\n\n## ماذا تُخزن وماذا ترسل مع كل تعديل\n\nيعمل القفل التفاؤلي فقط إذا حمل كل تعديل "علامة آخر مشاهدة" من لحظة فتح المستخدم للنموذج. يمكن أن تكون هذه العلامة رقم version أو طابع updated_at. بدونها، لا يستطيع الخادم معرفة ما إذا تغيّر السجل أثناء كتابة المستخدم.\n\nعلى السجل نفسه، احتفظ بالحقول التجارية الاعتيادية، بالإضافة إلى حقل تزامن يتحكم به الخادم. المجموعة الدنيا تبدو كالتالي:\n\n- id (معرّف ثابت)\n- الحقول التجارية (الاسم، الحالة، السعر، الملاحظات، إلخ)\n- version (عدد صحيح يزيد في كل تحديث ناجح) أو updated_at (طابع زمني يكتبه الخادم)\n\nعند تحميل شاشة التحرير، يجب على النموذج تخزين قيمة آخر مشاهدة لذلك الحقل التزامني. لا يجب أن يحرر المستخدم هذا الحقل، لذا اجعله مخفيًا أو في حالة النموذج. مثال: تُرجع الـ API version: 12، فيبقي النموذج 12 حتى الحفظ.\n\nعند النقر على حفظ، أرسل شيئين: التغييرات وعلامة آخر مشاهدة. الشكل الأبسط هو تضمينها في جسم طلب التحديث، مثل id، الحقول المعدّلة، وexpected_version (أو expected_updated_at). في AppMaster اعتبر هذا قيمة مرتبطة كما أي قيمة أخرى: اقرأها مع السجل، احتفظ بها دون تغيير، وأرسلها مع التحديث.\n\nعلى الخادم، يجب أن يكون التحديث شرطيًا. حدّث فقط إذا طابقت العلامة المتوقعة ما في قاعدة البيانات الآن. إذا لم تطابق، لا تدمج صامتًا.\n\nيجب أن تكون استجابة التعارض واضحة وسهلة المعالجة في الواجهة. استجابة عملية قد تتضمن:\n\n- حالة HTTP 409 Conflict\n- رسالة قصيرة مثل "تم تحديث هذا السجل بواسطة شخص آخر."\n- قيمة الخادم الحالية (current_version أو current_updated_at)\n- اختياريًا، السجل الكامل الحالي (حتى تعرض الواجهة ما تغيّر)\n\nمثال: فتح سام سجل عميل عند الإصدار 12. بريا تحفظ تغييرًا إلى الإصدار 13. سام يحاول الحفظ مع expected_version: 12. الخادم يرجع 409 مع السجل الحالي عند الإصدار 13. الآن تعرض الواجهة على سام مراجعة القيم بدلًا من الكتابة فوق تعديل بريا.\n\n## خطوة بخطوة: تطبيق القفل التفاؤلي من البداية للنهاية\n\nالقفل التفاؤلي يرجع عادةً إلى قاعدة واحدة: يجب أن يثبت كل تعديل أنه مبني على أحدث نسخة محفوظة من السجل.\n\n### 1) أضف حقل تزامن\n\nاختر حقلًا يتغير على كل عملية كتابة.\n\nحقل version المخصص بعدد صحيح أسهل للفهم. ابدأ من 1 وزِد على كل تحديث. إذا كان لديك updated_at موثوق يتغير على كل كتابة، يمكنك استخدامه بدلًا من ذلك، لكن تأكد من أنه يتغير على كل كتابة (بما في ذلك مهام الخلفية).\n\n### 2) أعد تلك القيمة للعميل عند القراءة\n\nعند فتح واجهة التحرير، أضف version (أو updated_at) في الاستجابة. خزنه في حالة النموذج كقيمة مخفية.\n\nفكّر فيه كإيصال يقول: "أنا أعدل ما قرأته آخر مرة."\n\n### 3) اجعل القيمة مطلوبة عند التحديث\n\nعند الحفظ، يرسل العميل الحقول المعدلة زائد قيمة آخر مشاهدة.\n\nعلى الخادم، اجعل التحديث شرطيًا. بعبارات SQL يكون:\n\n```sql

UPDATE tickets SET status = $1, version = version + 1 WHERE id = $2 AND version = $3; ```\n\nإذا أثّر التحديث على صف واحد، نجحت العملية. إذا أثّر على 0 صف، شخص آخر غيّر السجل بالفعل.\n\n### 4) أعد القيمة الجديدة بعد النجاح\n\nبعد حفظ ناجح، أعد السجل المحدث مع version الجديد (أو updated_at الجديد). على العميل استبدال حالة النموذج بما أرجعه الخادم. هذا يمنع "حفظ مزدوج" باستخدام إصدار قديم.\n\n### 5) عامل التعارضات كحالة طبيعية\n\nعندما يفشل التحديث الشرطي، أعد استجابة تعارض واضحة (غالبًا 409) تتضمن:\n\n- السجل الحالي كما هو الآن\n- تغييرات العميل المحاولة (أو معلومات كافية لإعادة بنائها)\n- الحقول التي تختلف (إن أمكن حسابها)\n\nفي AppMaster، يتوافق هذا جيدًا مع حقل نموذج PostgreSQL في Data Designer، نقطة قراءة تُرجع الإصدار، وBusiness Process ينفّذ التحديث الشرطي ويتفرّع إلى نجاح أو فرع تعارض.\n\n## أنماط واجهة المستخدم التي تتعامل مع التعارضات دون إزعاج المستخدم\n\nالقفل التفاؤلي نصف الحل فقط. النصف الآخر هو ما يراه المستخدم عندما يُرفض حفظه لأن شخصًا آخر غيّر السجل.\n\nواجهة التعارض الجيدة لها هدفان: إيقاف الكتابات الصامتة، ومساعدة المستخدم على إكمال مهمته بسرعة. إذا نُفّذت بشكل جيد، تبدو كحاجز مساعد لا كمطبة.\n\n### النمط 1: مربع حوار حازم وبسيط (الأسرع)\n\nاستخدم هذا عندما تكون التعديلات صغيرة، ويمكن للمستخدمين إعادة تطبيق تغييراتهم بعد إعادة التحميل بأمان.\n\nاجعل الرسالة قصيرة وواضحة: "تغيّر هذا السجل أثناء تحريرك. أعد التحميل لمشاهدة النسخة الأحدث." ثم قدم إجراءين واضحين:\n\n- إعادة التحميل والمتابعة (أساسي)\n- نسخ تغيّراتي (اختياري ومفيد)\n\nيمكن أن ينسخ "نسخ تغيّراتي" القيم غير المحفوظة للحافظة أو يحتفظ بها في النموذج بعد إعادة التحميل، حتى لا يضطر المستخدمون إلى تذكر ما كتبوه.\n\nيناسب هذا التحديثات أحادية الحقل، التبديلات، تغيّر الحالة، أو ملاحظات قصيرة. كما أنه الأسهل للتنفيذ في معظم الأدوات البنائية، بما فيها شاشات AppMaster.\n\n### النمط 2: "مراجعة التغييرات" (الأفضل للسجلات عالية القيمة)\n\nاستخدم هذا عندما يكون السجل مهمًا (التسعير، الأذونات، المدفوعات) أو النموذج طويلًا. بدلًا من خطأ مسدود، وجّه المستخدم إلى شاشة تعارض تقارن:\n\n- "تغيّراتك" (ما حاول حفظه)\n- "القيم الحالية" (الأحدث من قاعدة البيانات)\n- "ما تغيّر منذ فتحك" (الحقول المتعارضة)\n\nابقِ العرض مركزًا. لا تُظهر كل الحقول إذا كان النزاع يخص ثلاثة فقط.\n\nلكل حقل متعارض، قدم خيارات بسيطة:\n\n- احتفظ بِلِي\n- خذ بقيمهم\n- دمج (عندما يكون منطقيًا، مثل الوسوم أو الملاحظات)\n\nبعد حل المستخدم للتعارضات، احفظ مرة ثانية مع قيمة الإصدار الأحدث. إذا تدعم نصًا منسقًا أو ملاحظات طويلة، اعرض اختلافًا صغيرًا (إضافة/حذف) لتمكين قرار سريع.\n\n### متى تسمح بالكتابة القسرية (ومن يمكنه ذلك)\n\nأحيانًا يلزم الكتابة القسرية، لكنها يجب أن تكون نادرة ومتحكّمًا بها. إن أضفتها، اجعلها متعمدة: اطلب سببًا قصيرًا، سجّل من فعل ذلك، وحدّها لأدوار مثل المشرفين.\n\nللمستخدمين العاديين، افترض "مراجعة التغييرات." الكتابة القسرية مبررة عندما يكون المستخدم مالك السجل، أو السجل منخفض المخاطر، أو النظام يصحّح بيانات سيئة تحت إشراف.\n\n## سيناريو مثال: زميلان يعدّلان نفس السجل\n\nوكيلتا دعم، مايا وجوردان، تعملان في نفس أداة الإدارة. كلتاهما تفتح ملف عميل لتحديث الحالة وإضافة ملاحظات بعد مكالمات منفصلة.\n\nالجدول الزمني (مع القفل التفاؤلي مفعل باستخدام version أو فحص updated_at):\n\n- 10:02 - مايا تفتح العميل #4821. يحمل النموذج Status = "Needs follow-up"، Notes = "Called yesterday" وVersion = 7.\n- 10:03 - جوردان يفتح نفس العميل. يرى نفس البيانات، Version = 7.\n- 10:05 - مايا تغيّر Status إلى "Resolved" وتضيف ملاحظة: "Issue fixed, confirmed by customer." تضغط حفظ.\n- 10:05 - الخادم يحدث السجل، يزيد Version إلى 8 (أو يحدث updated_at)، ويسجل مدخلة تدقيق: من غيّر ماذا ومتى.\n- 10:09 - جوردان يكتب ملاحظة مختلفة: "Customer asked for a receipt" ويضغط حفظ.\n\nبدون فحص تزامن، قد يمحو حفظ جوردان تعديل مايا بصمت. مع القفل التفاؤلي، يرفض الخادم تحديث جوردان لأنه يحاول حفظ expected_version: 7 بينما السجل الآن عند Version = 8.\n\nيرى جوردان رسالة تعارض واضحة. تعرض الواجهة ما حدث وتقدّم خطوات آمنة:\n\n- إعادة تحميل السجل الأحدث (التخلي عن تعديلاتي)\n- تطبيق تغيّراتي فوق السجل الأحدث (مُستحسن إن أمكن)\n- مراجعة الاختلافات (عرض "لي" مقابل "الأحدث") واختيار ما يحتفظ به\n\nيمكن لشاشة بسيطة أن تعرض:\n\n- "تم تحديث هذا العميل بواسطة مايا في 10:05"\n- الحقول التي تغيّرت (Status وNotes)\n- معاينة لملاحظة جوردان غير المحفوظة حتى ينسخها أو يعيد تطبيقها\n\nيختار جوردان "مراجعة الاختلافات"، يحتفظ بحالة مايا = "Resolved"، ويلحق ملاحظته إلى الملاحظات القائمة. يحفظ مرة أخرى باستخدام Version = 8، ثم ينجح التحديث (الآن Version = 9).\n\nالحالة النهائية: لا فقدان بيانات، لا تخمين من استبدل من، ومسار تدقيق نظيف يُظهر تغيير مايا وحفظ كلتا الملاحظتين كتعديلات منفصلة وقابلة للتتبع. في أداة مبنية بـ AppMaster، يترجم هذا بوضوح إلى فحص واحد عند التحديث إضافةً إلى مربع حوار صغير لحل التعارض في واجهة الإدارة.\n\n## الأخطاء الشائعة التي لا تزال تسبب فقدان البيانات\n\nمعظم أخطاء "القفل التفاؤلي" ليست في الفكرة نفسها، بل في التسليم بين الواجهة، الـ API، والقاعدة. إذا نَسِي أي طبقة القواعد، فلا يزال بإمكانك الحصول على كتابة صامتة.\n\nخطأ كلاسيكي هو قراءة الإصدار عند تحميل شاشة التحرير، لكن عدم إرساله مع الحفظ. يحدث هذا عندما يُعاد استخدام نموذج عبر صفحات ويُسقط الحقل المخفي، أو عندما يرسل عميل الـ API الحقول "المعدّلة فقط".\n\nفخ آخر شائع هو إجراء فحوص التعارض فقط في المتصفح. قد يرى المستخدم تحذيرًا، لكن إذا قبل الخادم التحديث على أي حال، يمكن لعميل آخر (أو إعادة محاولة) أن يستبدل البيانات. يجب أن يكون الخادم الحارس النهائي.\n\nالأنماط التي تسبب معظم فقدان البيانات:\n\n- فقدان توكن التزامن في طلب الحفظ (version أو updated_at أو ETag)، فلا شيء للمقارنة على الخادم.\n- قبول التحديثات بدون شرط ذري، مثل التحديث بواسطة id فقط بدلًا من id + version.\n- استخدام updated_at بدقة منخفضة (مثل الثواني). يمكن لعمليتين في نفس الثانية أن تبدوان متطابقتين.\n- استبدال حقول كبيرة (ملاحظات، أوصاف) أو مصفوفات كاملة (وسوم، بنود) دون إظهار ما تغيّر.\n- التعامل مع أي تعارض كـ "حاول مرة أخرى فقط"، مما قد يعيد تطبيق قيم قديمة فوق بيانات أحدث.\n\nمثال ملموس: يفتح مشرفان نفس سجل العميل. أحدهما يضيف ملاحظة داخلية طويلة، والآخر يغيّر الحالة ويحفظ. إذا كان حفظك يستبدل الحمولة الكاملة للسجل، فقد تمحو التغيّر في الحالة الملاحظة الطويلة بطريق الخطأ.\n\nعندما يحدث تعارض، لا تزال الفرق تفقد بيانات إذا كانت استجابة الـ API سطحية. لا ترجع "409 Conflict" فقط. أعد ما يكفي ليصلحه إنسان:\n\n- إصدار الخادم الحالي (أو updated_at)\n- أحدث قيم الخادم للحقول المعنية\n- قائمة واضحة بالحقول المختلفة (حتى أسماء بسيطة)\n- من غيّرها ومتى (إن كنت تتعقب ذلك)\n\nإذا تنفذ هذا في AppMaster، اتّبع نفس الانضباط: احتفظ بالإصدار في حالة الواجهة، أرسله مع التحديث، وطبّق الفحص داخل منطق الخلفية قبل الكتابة إلى PostgreSQL.\n\n## فحوص سريعة قبل الإطلاق\n\nقبل النشر، قم بجولة سريعة للتحقق من أوضاع الفشل التي تخلق "تم الحفظ بنجاح" أثناء استبدال عمل شخص آخر بصمت.\n\n### فحوص البيانات والـ API\n\nتأكد أن السجل يحمل توكن تزامن من البداية للنهاية. يمكن أن يكون version عددًا صحيحًا أو updated_at، لكن يجب معاملته كجزء من السجل، لا كميتا-داتا اختيارية.\n\n- القراءات تتضمن التوكن (ويخزن UI ذلك في حالة النموذج، لا فقط على الشاشة).\n- كل تحديث يرسل التوكن الأخير المرئي، ويتحقق الخادم منه قبل الكتابة.\n- عند النجاح، يعيد الخادم التوكن الجديد كي يبقى UI متزامنًا.\n- التحديثات الدفعية والتحرير الداخلي تتبع نفس القاعدة، لا اختصار خاص.\n- مهام الخلفية التي تعدّل نفس الصفوف أيضًا تتحقق من التوكن (وإلا ستنشئ تعارضات تبدو عشوائية).\n\nفي AppMaster، تحقق من وجود حقل نموذج (version أو updated_at) في Data Designer، وأن Business Process للتحديث يقارنه قبل تنفيذ الكتابة الفعلية إلى PostgreSQL.\n\n### فحوص الواجهة\n\nالتعارض آمن فقط إذا كانت الخطوة التالية واضحة.\n\nعندما يرفض الخادم التحديث، عرض رسالة واضحة مثل: "تغيّر هذا السجل منذ فتحك له." ثم قدّم إجراءً آمنًا واحدًا أولًا: إعادة تحميل أحدث البيانات. إذا أمكن، أضف مسارًا "إعادة التحميل وإعادة تطبيق" يحفظ مدخلات المستخدم غير المحفوظة ويعيد تطبيقها على السجل المحدث، حتى لا يتحول تصحيح صغير إلى جلسة إعادة كتابة.\n\nإذا كانت لديك حاجة حقيقية، أضف خيار "حفظ قسري" مضبوط. قيده بدور المستخدم، أكّد الإجراء، وسجّل من أجبر الحفظ وما تغيّر. هذا يسمح بالطوارئ من دون جعل فقدان البيانات افتراضيًا.\n\n## الخطوات التالية: أضف القفل إلى سير عمل واحد ووسّع\n\nابدأ صغيرًا. اختر شاشة إدارة واحدة يتصادم فيها المستخدمون عادة، وأضف القفل التفاؤلي هناك أولًا. مناطق التصادم العالية عادةً التذاكر، الطلبات، التسعير والمخزون. إذا جعلت التعارضات آمنة في شاشة مزدحمة واحدة، سترى بسرعة النمط الذي يمكن تكراره في أماكن أخرى.\n\nاختر سلوك التعارض الافتراضي مقدمًا، لأنه يشكل منطق الخلفية والواجهة:\n\n- وقف وإعادة تحميل: أوقف الحفظ، أعد تحميل السجل الأحدث، واطلب من المستخدم إعادة تطبيق تغييره.\n- مراجعة ودمج: عرض "تغيّراتك" مقابل "الأحدث" ودع المستخدم يقرر ما يحتفظ به.\n\nوقف وإعادة التحميل أسرع للبناء ويناسب التعديلات القصيرة. المراجعة والدمج تستحق العناء عندما تكون السجلات طويلة أو عالية المخاطر.\n\nثم نفّذ واختبر سيرًا واحدًا كاملًا قبل التوسيع:\n\n- اختر شاشة واحدة وحدد الحقول الأكثر تعديلًا.\n- أضف قيمة الإصدار (أو updated_at) إلى حمولة النموذج وتطلبها عند الحفظ.\n- اجعل التحديث شرطياً في كتابة قاعدة البيانات (تحديث فقط إذا طابق الإصدار).\n- صمم رسالة التعارض والخطوة التالية (إعادة التحميل، نسخ نصي، فتح عرض مقارنة).\n- اختبر مع متصفحين: احفظ في التبويب A، ثم حاول حفظ بيانات قديمة في التبويب B.\n\nأضف تسجيلًا خفيفًا للتعارضات. حتى حدث بسيط "وقع تعارض" مع نوع السجل واسم الشاشة ودور المستخدم يساعدك على كشف البقع الساخنة.\n\nإذا تبنيت AppMaster، تتطابق الأجزاء الرئيسية بسلاسة: صنّف حقل version في Data Designer، نفّذ تحديثات شرطية في Business Processes، وأضف مربع حوار صغير لحل التعارض في UI builder. بعد ثبات أول سير عمل، كرر النمط شاشة بشاشة، وحافظ على واجهة تعارض موحدة حتى يتعلم المستخدمها مرة واحدة ويثق بها في كل مكان.

الأسئلة الشائعة

ما هي "الكتابة الصامتة" ولماذا تحدث؟

حدوث الكتابة الصامتة يعني أن شخصين يحرّران نفس السجل من علامات تبويب أو جلسات مختلفة، والحفظ الأخير يستبدل تغييرات السابق بدون أي تحذير. الجزء الخطير أن كلا المستخدمين قد يرى "حفظ ناجح"، فيكتشف النقص لاحقًا فقط.

ما الذي يفعله القفل التفاؤلي بعبارات بسيطة؟

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

لماذا لا نقفل السجل ببساطة حتى لا يتمكن الآخرون من تحريره؟

الاقفال التشدّدي (pessimistic locking) يمنع الآخرين من التحرير أثناء عملك، ما يؤدي غالبًا إلى انتظار، مهلات، وتساؤلات حول من قفل السجل. القفل التفاؤلي يناسب لوحات الإدارة عادة لأن الأشخاص يعملون بالتوازي وتظهر المشكلات فقط عند التصادم.

هل أستخدم رقم إصدار أم `updated_at` لفحص التعارض؟

عمود الإصدار يكون عادة أبسط وأوضح لأنه يتجنّب مشاكل دقة الطوابع الزمنية والاختلاف بين ساعات الأنظمة. فحص updated_at قد ينفع لكنه قد يفشل في حالات الدقة المنخفضة أو انحراف الساعة بين الأنظمة.

ما البيانات التي يجب إدراجها لجعل القفل التفاؤلي يعمل؟

تحتاج إلى توكن تزامني يتحكم به الخادم على السجل، عادة version (عدد صحيح) أو updated_at (طابع زمني). يجب على العميل قراءته عند فتح النموذج، الاحتفاظ به دون تغيّر، وإرساله مع عملية الحفظ كالقيمة المتوقعة.

لماذا يجب أن يجرى فحص الإصدار على الخادم وليس فقط في الواجهة؟

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

ماذا ينبغي أن يرى المستخدم عند حدوث تعارض؟

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

ماذا يجب أن تعيد الـ API عند حدوث تعارض لمساعدة الواجهة على الاسترداد؟

اِرجع استجابة تعارض واضحة (غالبًا 409) مع سياق كافٍ للتعافي: إصدار الخادم الحالي وقيم الحقول الأخيرة. إذا أمكن، ضمّن من عدله ومتى حتى يفهم المستخدم سبب رفض الحفظ.

ما أكثر الأخطاء الشائعة التي لا تزال تؤدي إلى فقدان البيانات؟

انتبه لعدم إرسال التوكن عند الحفظ، التحديثات التي تُصدر بواسطة فلتر id فقط، واستخدام طوابع زمنية بدقة منخفضة. أيضًا تجنّب استبدال الحقول الكبيرة أو المصفوفات الكاملة دون توضيح ما تغيّر.

كيف أطبق هذا في AppMaster دون برمجة مخصصة؟

في AppMaster، أضف حقل version في Data Designer وقرّأيها في حالة النموذج داخل الواجهة. ثم نفّذ تحديثًا شرطيًا في Business Process ينجح فقط عند مطابقة الإصدار المتوقع، وتعامل مع فرع التعارض في الواجهة عبر مسار إعادة التحميل أو المراجعة.

من السهل أن تبدأ
أنشئ شيئًا رائعًا

تجربة مع AppMaster مع خطة مجانية.
عندما تكون جاهزًا ، يمكنك اختيار الاشتراك المناسب.

البدء
القفل التفاؤلي لأدوات الإدارة: منع الكتابات الصامتة | AppMaster