تحديثات المشاريع
3 دقائق قراءة
تصميم المزامنة دون اتصال في Basket
قبو متجر بقالة مكان سيئ للاعتماد على الشبكة. إليك كيف يتشكّل طابور المزامنة دون اتصال في Basket، وأين تكمن القرارات الصعبة.
معظم تطبيقات القوائم تفترض وجود اتصال. تفتح التطبيق، وتُحمّل القائمة، وتضغط على عنصر، ثم تنتظر الخادم ليؤكد. ينجح هذا حتى تدخل الطابق السفلي في سوبرماركت حيث تختفي الإشارة بين المدخل وممر منتجات الألبان.
Basket قائمة تسوق مشتركة لأسرة. الفكرة كلها أن تكون القائمة صحيحة للجميع، وهذا يجعل انقطاع الاتصال مشكلة في المنتج لا مجرد إزعاج.
النسخة الساذجة
الإصدار الأول من أي تطبيق قوائم يستدعي الـ API عند كل تغيير. تُخفي الواجهة المتفائلة زمن الاستجابة: تتبدّل علامة الاختيار فورًا، ويخرج الطلب في الخلفية. وإذا فشل، إما تعيد المحاولة بصمت أو تعرض خطأً.
يفشل هذا النهج بطريقة محددة ومزعجة. إذا لم يصل الطلب قط وكان المستخدم قد غادر الشاشة، تقول الحالة المحلية «تم الشراء» بينما يقول الخادم «لم يُشترَ». ومن يفتح القائمة بعد ذلك يرى واقعًا مختلفًا. اضرب ذلك في شخصين في ممرين مختلفين، تصبح القائمة عديمة الفائدة.
إذن السؤال ليس «كيف نعيد محاولة الطلب»، بل «ماذا يعتقد الجهاز، وكيف نجعل هذا الاعتقاد يتقارب».
المحلي أولًا، والخادم هو المرجع
النموذج الذي استقررت عليه يفصل بين فكرتين:
- يحتفظ الجهاز بنسخته الخاصة من القائمة ويطبّق كل تغيير محليًا وفورًا.
- الخادم هو مصدر الحقيقة عند حل التعارضات، لكنه ليس شرطًا لاستخدام التطبيق.
كل تعديل يُضاف إلى طابور بمعرّف يولّده العميل. وعندما يعود الاتصال، يُعاد تشغيل الطابور بالترتيب.
{
"opId": "01J9Z6W4K2",
"listId": "list_household",
"type": "item.claim",
"itemId": "item_milk",
"clientId": "device_android",
"createdAt": "2026-10-24T14:02:11.482Z",
"payload": { "claimedBy": "member_2" }
}معرّف العملية مهم لسببين. فهو يجعل إعادة التشغيل آمنة عند التكرار إذا كان الخادم قد عالج الطلب وضاعت الاستجابة، ويمنح سجل النشاط شيئًا ثابتًا يُشار إليه.
إعادة التشغيل هي النصف السهل
إعادة تشغيل طابور بالترتيب أمر مباشر. الجزء الصعب هو ما يحدث عندما يحتوي طابوران على عمليتين متناقضتين.
يتسوق عضوان في الوقت نفسه. أحدهما يطالب بالحليب، والآخر يعلّمه كمُشترى وهو دون اتصال. كلتا العمليتين صحيحتان من منظور الجهاز الذي نفّذها.
القاعدة الأولى التي اعتمدتها: يحلّ الخادم وفق ترتيب الوصول، ويقبل العميل حالة الخادم بعد إعادة التشغيل. هذا بسيط وقابل للتفسير وينتج نتيجة متوقعة. لكنه مفاجئ أحيانًا، ولهذا يعرض التطبيق ملاحظة قصيرة عندما يتجاوز الخادم تغييرًا محليًا.
القاعدة الثانية: تُحلّ التعارضات لكل عنصر لا لكل قائمة. إذا عدّل أحدهم الحليب وعدّل آخر الخبز، فلا يجب أن يخسر أي منهما تغييره.
القاعدة الثالثة تفضيل لا ضمان: عندما تكون عمليتان متساويتين في الصلاحية، تفوز الأكثر تحديدًا. المطالبة أكثر تحديدًا من التعليم كمُشترى، لأنها تحمل نية حول مَن يشتري الشيء.
ما أتجنّبه عن قصد
لا أبني طبقة مزامنة قائمة على CRDT. فهي الجواب الصحيح لسؤال أصعب، وستضيف صنفًا من الأخطاء لا أريد تصحيحه من داخل سوبرماركت.
ولا أعتمد المزامنة المباشرة بين الأجهزة. يبقى الخادم طرفًا، لأن أحدًا يجب أن يكون صاحب القول الفصل، وقائمة منزلية لا تحتاج إلى بروتوكول توافق موزّع.
الجزء الذي لم يُحسم بعد
الطابور يعمل. وقواعد المواءمة مكتوبة. يبقى سؤال كيف ينبغي للواجهة أن تنقل التعارض دون أن تصبح صاخبة. إشعار عند كل تجاوز سيكون فظيعًا. والصمت سيكون أسوأ.
الاتجاه الحالي هو قيد في سجل النشاط يكون ظاهرًا لكنه غير مقاطع، مع مؤشر خفيف على العنصر المتأثر. هذه مشكلة تصميم متنكّرة في زي مشكلة مزامنة، وهو ما يشبه معظم ما شعرت به أثناء بناء هذا التطبيق.
إن أردت متابعة المشروع، فإن صفحة المشروع هي المكان الذي أحتفظ فيه بالحالة الراهنة.
بقلم
مهند بكار
متخصص تقني ومفكّر أنظمة وبانٍ يعمل بيديه عبر البرمجيات والتكاملات والتسليم التقني.
المقال السابق
ما علّمني إياه المختبر المنزلي عن DNSمقالات ذات صلة
كل المقالاتبنية تحتية يكون فيها الإنترنت اختياريًا
ملاحظات من تخطيط المراقبة لعقار ريفي يعاني انقطاع الكهرباء والاتصال.
ما علّمني إياه المختبر المنزلي عن DNS
حين يسقط خادم التحليل، يسقط كل شيء معه. كان الانقطاع من صنع يدي، وقد رسخ الدرس.
التكاملات تفشل عند الأطراف
المسار السعيد هو الجزء السهل. معظم مشكلات التكامل تكمن في الطوابع الزمنية وإعادة المحاولة والهوية والافتراضات حول مَن يملك البيانات.