الشبكة ضعيفة؟ القصة ليست مجرد Service Worker
في ليلة من أكتوبر الماضي، كنت أجلس في غرفة فندق بمدينة ليشبونة عشية تقديم العرض التوضيحي لأداة إدارة مشاريع عمل عليها فريقي لأربعة أشهر كاملة. كان شبكة الواي فاي تعرض علامة الاتصال بينما لا شيء يفتح فعلياً. شاهدت تطبيقنا—الذي كنت فخوراً به جداً—يعرض شاشة بيضاء فارغة مع مؤشر التحميل الدوار. ثم خطأ انتهاء وقت الطلب. ثم لا شيء.
ربطت الحواسيب بنقطة اتصال الهاتف. عمل التطبيق، لكن كل نقرة كانت تتطلب الانتظار لثانيتين. إنشاء مهمة؟ انتظار. نقل بطاقة بين الأعمدة؟ انتظار. جلست هناك والتفكير ينهكني: بنينا واجهة مستخدم في React، وخلفية برمجة في Node، وقاعدة بيانات Postgres، وتخزين مؤقت في Redis، وشبكة GraphQL بستة محلات حلول لمجرد لوحة المهام. كل هذه البنية التحتية، والتطبيق اللعين لا يستطيع عرض بياناتي لي بدون رحلة ذهاب وإياب لخادم يبعد 3,000 ميل!
تلك الليلة كانت بداية تعمقي الجاد في عمارة المحلي أولاً (Local-First). ليس لأنني قرأت مقالاً تسويقياً، بل لأنني شعرت بالإحراج الفعلي أمام عميل مستقبلي.
دعنا نكون صريحين: قضيت عامي الأول في استبعاد هذه العمارة باعتبارها خيالاً أكاديمياً. قرأت ورقة تحالف Ink & Switch الشهيرة الصادرة عام 2019 وفكرت حينها: بحث رائع، لكنه غير عملي للتطبيق الحقيقي. كنت مخطئاً. الأدوات المتاحة عام 2019 لم تكن ناضجة بالفعل. المبادئ السبعة التي طرحتها الورقة—السرعة، دعم الأجهزة المتعددة، العمل دون إنترنت، التعاون، الاستدامة، الخصوصية، وملكيات المستخدم للبيانات—كانت تبدو لي أمنيات وليست متطلبات هندسية. بعد سنوات وشحن ثلاثة تطبيقات إنتاجية بهذه الأنماط، أصبحت لدي قناعات راسخة سأشاركها معك هنا.

الفرق الجوهري: المحلي أولاً ليس مجرد Offline-First
هناك خلط مستمر يجب إنهائه في مجتمعات التطوير. تطبيق المحلي أولاً ليس مجرد تطبيق يعمل بدون إنترنت (Offline-First)، وليس مجرد إضافة ملف Service Worker ونادِ الأمر ناجحاً. كما أنه ليس مرادفاً لتطبيقات الويب التقدمية PWA.
نهج Offline-First يعني أن تطبيقك يتعامل مع انقطاع الشبكة بأناقة، لكن الخادم يظل هو المصدر الوحيد للحقائق. عندما تعود الشبكة، يفوز الخادم دائماً. التخزين المؤقت عبر Service Worker هو تحسين للأداء فقط؛ أنت تعرض بيانات قديمة بسرعة، لكنك لم تغير مجرى ملكية البيانات.
المحلي أولاً هو عمارة بيانات كاملة (Data Architecture). جهاز المستخدم يحتفظ بالنسخة الأصلية والأولية لبياناته. التطبيق يقرأ ويكتب محلياً على قاعدة بيانات سريعة داخل المتصفح. العرض يتم فوراً بدون أي تأخير زر. عملية المزامنة مع الخوادم أو الأجهزة الأخرى تحدث في الخلفية. الخادم—إن وجد—هو مجرد عقدة مزامنة تتمتع ببعض السلطات الخاصة مثل التحقق من الهوية والنسخ الاحتياطي.
وهنا الفكرة المفصلية: العميل ليس مجرد واجهة عرض نحيفة تطلب الإذن من الخادم لعرض البيانات، بل هو عقدة مستقلة في نظام موزع تملك قاعدة بياناتها الخاصة.
متى يجب أن تهرب فوراً من هذا الخيار؟
أضع هذا التحذير مبكراً لأنني رأيت مطورين يتحمسون لبنية جديدة ويقحمونها في مشاريع لا تناسبها مطلقاً. ضيعت أسرع من ستة أسابيع في محاولة تطبيق المحلي أولاً على لوحة تحكم تحليلات داخلية في عمل سابق، حتى جاءتني زميلتي وقالت لي: البيانات تُنشأ على الخادم أصلاً، لا يوجد شيء لننسخه للعميل، ماذا تفعل؟ وكانت محقة تماماً.
إليك الحالات التي تكون فيها هذه العمارة خياراً سيئاً:
- البيانات التي ينشئها الخادم بالكامل: لوحات التحليلات، تغذيات الأخبار الاجتماعية، نتائج البحث.
- الأنظمة التي تتطلب اتساقاً معاملاتيّاً صارماً (ACID): المعاملات البنكية، معالجة الدفع، إدارة مخزون حجز التذاكر. إن حاول شخصان شراء آخر قطعة في المخزن، فأنت بحاجة لقاعدة بيانات مركزية تتخذ القرار بنعم أو لا. الاتساق النهائي هنا سيكلفك أموالاً.
- تطبيقات CRUD البسيطة: لوحة إدارة داخلية يستخدمها خمسة أشخاص في مكتب بشركة تتمتع بإنترنت ممتاز. إضافة محرك مزامنة هنا هو تعقيد هندسي لا معنى له.
والنتيجة؟ هي عمارة مذهلة لتطبيقات تدوين الملاحظات، تحرير المستندات، أدوات التصميم التعاوني، إدارة المهام، والتطبيقات الميدانية التي تعمل ببيئات شبكية سيئة.

نسخ متماثلة وليس طلبات: نموذج Git للبيانات
إذا كنت تستخدم نظام Git، فأنت تفهم النموذج الفكري مسبقاً. الأنساق القديمة المركزية مثل SVN كانت تتطلب الاتصال بالخادم للقيام بأي إيداع أو مراجعة للتاريخ. جاء Git وأعطى كل مطور نسخة كاملة. تقوم بالإيداع محلياً، والتفريع محلياً، والدمج محلياً، ثم تدفع وتجذب التغييرات متى ما أردت.
تطوير المحلي أولاً هو تطبيق مفهوم Git على بيانات التطبيق. كل جهاز عميل يحمل نسخة (كاملة أو جزئية) من البيانات ذات الصلة. عمليات الكتابة تتم محلياً فوراً. المزامنة هي عملية دفع وسحب في الخلفية. والنزاعات يتم حلها عبر استراتيجيات دمج محددة.
في العمارة التقليدية، إضافة مهمة في قائمة تدعو للقيام بطلب POST للخادم، والانتظار، وفي حال النجاح نحدث الواجهة. في عمارة المحلي أولاً: نكتب في قاعدة بيانات SQLite المحلية، وانتهى الأمر. واجهة المستخدم تتحدث فوراً لأنها تقرأ من قاعدة البيانات المحلية ذاتها. لا وجود لحالات التحميل، ولا منطق للتحديثات التفاؤلية (Optimistic UI) لأن الكتابة المحلية هي الحقيقة الحالية فعلاً.
أين تعيش البيانات داخل المتصفح؟
انسَ التخزين المحلي localStorage. إنه متزامن يغلق خيط التنفيذ الرئيسي، ومحدود بسعة 5 ميجابايت، ولا يخزن سوى النصوص. إنه ممتاز لتفضيل المظهر الداكن فقط، وليس قاعدة بيانات.
تكنولوجيا IndexedDB هي العمود الفقري القديم الذي لا يحبه أحد. متوفرة في كل المتصفحات ومتزامنة وتتحمل مئات الميجابايت، لكن واجهة برمجة التطبيقات الخاصة بها معقدة ومزعجة. الخيار الحديث والحقيقي اليوم هو تشغيل قاعدة بيانات SQLite داخل المتصفح عبر WebAssembly (WASM).

تجميع SQLite إلى WASM وحفظ البيانات على نظام الملفات الخاص المستقل Origin Private File System (OPFS) يوفر لك قاعدة بيانات علاقات حقيقية في المتصفح. استعلامات SQL كاملة، معاملات، وفهارس. واجهة OPFS تمنح التطبيقات وصولاً عالي الأداء ومباشراً عبر خيوط Web Workers.
لكن احذر من تفاصيل الممارسة: تطبيق OPFS في متصفح Safari يتصرف بطرق مختلفة ومفاجئة عن Chrome. واجهت ثغرة حيث يفشل إنشاء مقبض الوصول المتزامن صامتاً داخل أطر iframe على Safari. الحل كان التحول لطبقة دمج احتياطية تعتمد على IndexedDB عند كشف المتصفح. يجب دائماً تغليف عمليات الكتابة بمصفوفة معالجة متتالية لمنع التعارضات.
المزامنة وحل النزاعات: الجزء المعقد حقاً
تخزين البيانات محلياً مشكلة تم حلها. مزامنتها بأسلوب موثوق عبر عدة أجهزة هي المكان الذي يظهر فيه التعقيد الهندسي الحقيقي. عندما تتمكن عدة نسخ من الكتابة بشكل مستقل، فأنت بحاجة لآلية لمصالحة التعديلات.
الخيار الأكثر شيوعاً للنصوص والتحرير التعاوني هو CRDTs (أنواع البيانات المكررة خالية النزاع). هي بنيات بيانات مصممة رياضياً بحيث يمكن دمج التعديلات المتزامنة دائماً بدون تعارض. مكتبة Yjs تعتبر الخيار الذهبي في بيئة JavaScript للتعديل التعاوني اللحظي.

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