العودة للدورة|إتقان مقابلات تصميم الأنظمة: بنية قابلة للتوسع من التقدير إلى الإنتاج
معمل

ابنِ واجهة خلفية لمستند تعاوني

45 دقيقة
متقدم
محاولات مجانية غير محدودة

التعليمات

في هذا المختبر، ستبني واجهة خلفية كاملة لتحرير المستندات التعاونية بلغة Python. ستنفذ CRDTs للدمج الخالي من التعارضات، ومدير اتصالات WebSocket للاتصال الفوري، ومدير مستندات لتنسيق الحالة، ونظام حضور لتتبع المستخدمين النشطين ومواضع المؤشرات.

هذه هي البنية خلف أنظمة مثل Google Docs وFigma وNotion.

نظرة عامة على البنية

العميل A ──WebSocket──> مدير الاتصالات ──> معالج الرسائل
العميل B ──WebSocket──>      |                    |
العميل C ──WebSocket──>      |                    |
                             |                    v
                        نبضات القلب          مدير المستندات
                        وإعادة الاتصال            |
                             |              ┌──────┼──────┐
                             v              v      v      v
                        متتبع            G-Counter  LWW   Text CRDT
                        الحضور           PN-Counter  Reg   (RGA)

ما ستبنيه

ستنفذ 8 ملفات تشكل معًا واجهة خلفية عاملة للتحرير التعاوني:

  1. server/crdt/counter.py — CRDTs للعدادات: G-Counter وPN-Counter مع عمليات الدمج
  2. server/crdt/lww_register.py — سجل آخر كاتب يفوز مع حل التعارضات بالطابع الزمني
  3. server/crdt/text_crdt.py — CRDT نصي (مبني على RGA) يدعم الإدراج والحذف والتحرير المتزامن
  4. server/document/document_manager.py — إدارة حالة المستند: تطبيق العمليات، دمج النسخ، إنشاء الفروقات
  5. server/websocket/connection_manager.py — مجمع اتصالات WebSocket: اتصال، قطع، نبضات قلب، بث
  6. server/websocket/message_handler.py — توجيه الرسائل: تحليل العمليات الواردة، تطبيقها على المستند، بث التحديثات
  7. server/presence/presence_tracker.py — نظام الحضور: تتبع المستخدمين النشطين لكل مستند، مواضع المؤشرات، كشف الخمول
  8. server/main.py — نقطة الدخول: بدء الخادم، إدارة الاتصالات، إدارة المستندات

دليل خطوة بخطوة

الخطوة 1: CRDTs العدادات (الملف 1)

نفّذ GCounter وPNCounter. عداد G-Counter يتتبع عدادًا واحدًا لكل عقدة. قيمته هي مجموع جميع العدادات. الدمج يأخذ الحد الأقصى لكل عنصر. عداد PN-Counter يستخدم عدادي G-Counter — واحد للزيادات (p)، وواحد للنقصانات (n) — لذا القيمة هي p.value() - n.value().

الخصائص الأساسية التي يجب الحفاظ عليها:

  • merge() تبادلية: a.merge(b) تنتج نفس الحالة مثل b.merge(a)
  • merge() عديمة القوة: a.merge(b); a.merge(b) تنتج نفس الحالة مثل دمج واحد
  • قيمة العداد دائمًا متسقة نهائيًا بعد الدمج

الخطوة 2: LWW-Register (الملف 2)

نفّذ سجل آخر كاتب يفوز الذي يخزن قيمة واحدة. عندما تتعارض كتابتان، الكتابة ذات الطابع الزمني الأعلى تفوز. إذا تساوت الطوابع الزمنية، استخدم معرّف العقدة ككاسر تعادل حتمي (مقارنة معجمية).

الخطوة 3: CRDT النصي (الملف 3)

نفّذ CRDT نصيًا مبنيًا على RGA. كل حرف يُمثل كعقدة مع:

  • معرّف فريد: (node_id, sequence_number)
  • قيمة الحرف
  • مرجع للعقدة التي أُدرج بعدها
  • علامة شاهد للأحرف المحذوفة

الإدراج يضع حرفًا جديدًا بعد موضع مرجعي. إذا استهدف إدراجان متزامنان نفس الموضع، الإدراج ذو معرّف العقدة الأعلى يأتي أولاً (ترتيب حتمي). الحذف يُعلّم الحرف كشاهد (يبقى في الهيكل لكنه غير مرئي).

طريقة to_string() تُرجع فقط الأحرف غير المحذوفة بالترتيب.

الخطوة 4: مدير المستندات (الملف 4)

مدير المستندات يتتبع حالة المستند باستخدام CRDTs من الخطوات 1-3. يدعم:

  • تطبيق العمليات الفردية (insert, delete, set_title, increment_counter)
  • دمج حالة نسخة كاملة من عقدة أخرى
  • إنشاء فرق بين الحالة الحالية ونسخة سابقة
  • إرجاع لقطة من حالة المستند الكاملة

الخطوة 5: مدير اتصالات WebSocket (الملف 5)

ابنِ مدير اتصالات يتتبع اتصالات WebSocket لكل مستند. يجب أن:

  • يسجل ويلغي تسجيل الاتصالات (ربط معرّفات العملاء بكائنات WebSocket)
  • يتتبع أي مستند يحرره كل عميل
  • يرسل نبضات قلب ويكتشف الاتصالات الميتة (نبضات قلب مفقودة > العتبة)
  • يبث الرسائل لجميع العملاء الذين يحررون نفس المستند (باستثناء المرسل)

الخطوة 6: معالج الرسائل (الملف 6)

معالج الرسائل يستقبل رسائل WebSocket الخام (JSON)، يحللها إلى عمليات مُنوّعة، يطبقها على المستند عبر مدير المستندات، ويبث النتيجة للعملاء المتصلين الآخرين. يجب أن يتعامل مع الرسائل المشوهة برشاقة.

أنواع الرسائل المدعومة: insert، delete، set_title، cursor_move، presence_update.

الخطوة 7: متتبع الحضور (الملف 7)

تتبع المستخدمين النشطين لكل مستند مع:

  • موضع المؤشر (سطر، عمود) لكل مستخدم
  • طابع زمني لآخر نشاط لكشف الخمول
  • عتبة خمول (مثلاً 5 دقائق) — المستخدمون الذين يتجاوزونها يُعلّمون كخاملين
  • طرق للحصول على جميع المستخدمين النشطين (غير الخاملين) لمستند

الخطوة 8: نقطة الدخول الرئيسية (الملف 8)

اربط جميع المكونات معًا. فئة CollaborativeServer يجب أن:

  • تهيّئ مدير المستندات ومدير الاتصالات ومعالج الرسائل ومتتبع الحضور
  • تتعامل مع اتصالات العملاء الجديدة (تسجيل، إرسال حالة المستند الحالية)
  • تتعامل مع قطع الاتصال (إلغاء التسجيل، إزالة الحضور)
  • توجه الرسائل الواردة لمعالج الرسائل
  • توفر طريقة للحصول على حالة الخادم (العملاء المتصلون، المستندات النشطة)

تلميحات

  • لـ CRDT النصي، خزّن الأحرف في قائمة واستخدم بحثًا خطيًا لعمليات البحث عن الموضع. هذا O(n) لكنه صحيح وبسيط — التطبيقات الحقيقية تستخدم الأشجار لـ O(log n).
  • استخدم time.time() للطوابع الزمنية في كل مكان. في الإنتاج، ستستخدم ساعات متجهية أو ساعات منطقية هجينة.
  • كائنات WebSocket في هذا المختبر محاكاة — لا تحتاج مكتبة WebSocket فعلية. استخدم واجهة MockWebSocket المقدمة في الكود الأولي.
  • جميع طرق merge() في CRDTs يجب أن تكون تبادلية وعديمة القوة. اختبر بدمج A في B ثم B في A — كلاهما يجب أن ينتجا حالات متطابقة.
  • استخدم تلميحات الأنواع في Python على جميع الطرق العامة.

معايير التقييم

G-Counter وPN-Counter مع دلالات دمج صحيحة: GCounter يتتبع عدادات لكل عقدة، الزيادة تزيد فقط عداد العقدة المحلية، القيمة تُرجع المجموع، والدمج يأخذ الحد الأقصى لكل عنصر. PNCounter يستخدم عدادي GCounter حيث القيمة = p - n، والدمج يُفوّض لكلا العدادين الداخليين. عمليتا الدمج تبادليتان وعديمتا القوة.10 نقاط
LWW-Register مع حل التعارضات بالطابع الزمني: set() تُحدّث فقط عندما يكون الطابع الزمني الجديد أعلى بشكل صارم، أو عندما تتساوى الطوابع الزمنية ومعرّف العقدة يعمل ككاسر تعادل حتمي (مقارنة معجمية). merge() تتبنى بشكل صحيح حالة السجل الآخر عندما يفوز. get_state() تُرجع صف متسق من (القيمة، الطابع الزمني، معرّف عقدة الكاتب).10 نقاط
CRDT نصي يدعم الإدراج/الحذف المتزامن مع التقارب: تنفيذ مبني على RGA حيث كل حرف له معرّف فريد (node_id, seq). insert() تضع الأحرف بعد موضع مرجعي. الإدراجات المتزامنة في نفس الموضع تُرتب بشكل حتمي بمعرّف العقدة. delete() تستخدم الشواهد. to_string() تُرجع النص المرئي فقط. merge() تدمج العقد البعيدة وتطبق الشواهد، منتجة حالة متطابقة بغض النظر عن ترتيب الدمج.20 نقاط
مدير المستندات يطبق العمليات ويدمج حالات النسخ: apply_operation() توجه بشكل صحيح insert وdelete وset_title وincrement_counter إلى CRDTs المناسبة. الإصدار يتزايد مع كل عملية. merge_replica() تقبل حالات CRDT المتسلسلة وتدمجها. get_snapshot() تُرجع DocumentSnapshot متسقة. get_diff() تُرجع العمليات وتغييرات المحتوى بين الإصدارات.15 نقاط
مدير اتصالات WebSocket مع نبضات القلب وإعادة الاتصال: connect() تسجل عميلًا مع مستند وتخزن websocket. disconnect() تزيل العميل وتنظف ارتباطات المستندات وتغلق websocket. heartbeat() تُحدّث طابع آخر نبضة. broadcast() ترسل رسائل JSON لجميع أقران المستند باستثناء المرسل. cleanup_dead_connections() تكتشف وتزيل العملاء الذين فاتتهم نبضات كثيرة.15 نقاط
معالج الرسائل يوجه العمليات ويبث: parse_message() تحلل JSON بأمان، تتحقق من الحقول المطلوبة (type, user_id, doc_id)، وترفض الرسائل المشوهة برشاقة. handle_message() توجه للمعالج الصحيح بناءً على نوع الرسالة (insert/delete/set_title تذهب لمدير المستندات، cursor_move/presence_update تذهب لمتتبع الحضور). النتائج تُبث للعملاء المتصلين الآخرين. استجابات الخطأ تتضمن رسائل وصفية.10 نقاط
متتبع الحضور مع تتبع المستخدمين لكل مستند ومزامنة المؤشر: join() تسجل مستخدمًا مع مستند وتُعيّن لون مؤشر فريد. leave() تزيل المستخدم وتنظف. update_cursor() تخزن موضع السطر والعمود وتعيد ضبط مؤقت الخمول. check_idle() تكتشف المستخدمين الذين تجاوزوا عتبة الخمول وتُرجع معرّفات المستخدمين الخاملين حديثًا. get_active_users() تُرجع فقط المستخدمين غير الخاملين. get_cursor_positions() تُرجع قاموس مؤشرات المستخدمين النشطين.10 نقاط
نقطة الدخول الرئيسية تربط جميع المكونات: CollaborativeServer تهيئ ConnectionManager وPresenceTracker وMessageHandler وقاموس doc_managers. handle_connect() تسجل websocket وتنشئ/تحصل على مدير المستندات وتسجل الحضور وترسل الحالة الحالية وتبث user_joined. handle_disconnect() تزيل الاتصال وتغادر الحضور وتبث user_left. handle_message() تسجل نبضة القلب وتوجه لـ MessageHandler وتزيد عداد العمليات. get_status() تُرجع العملاء المتصلين والمستندات النشطة وإجمالي العمليات ووقت التشغيل.10 نقاط

قائمة التحقق

0/9

حلك

محاولات مجانية غير محدودة
نشرة أسبوعية مجانية

ابقَ على مسار النيرد

بريد واحد أسبوعياً — دورات، مقالات معمّقة، أدوات، وتجارب ذكاء اصطناعي.

بدون إزعاج. إلغاء الاشتراك في أي وقت.