يتناول هذا الدليل بعض المفاهيم الأساسية في بنية البيانات وأفضل الممارسات لتنظيم بيانات JSON في Firebase Realtime Database.
يتطلب إنشاء قاعدة بيانات منظمة بشكل صحيح قدرًا كبيرًا من التفكير المسبق. الأهم من ذلك، أنك بحاجة إلى التخطيط لكيفية حفظ البيانات واسترجاعها لاحقًا لجعل هذه العملية سهلة قدر الإمكان.
كيفية تنظيم البيانات: إنّها شجرة JSON
يتم تخزين جميع بيانات Firebase Realtime Database كعناصر JSON. يمكنك اعتبار قاعدة البيانات
شجرة JSON مستضافة على السحابة الإلكترونية. على عكس قاعدة بيانات SQL، لا توجد
جداول أو سجلات. عند إضافة بيانات إلى شجرة JSON، تصبح عقدة في بنية JSON الحالية مع مفتاح مرتبط. يمكنك تقديم مفاتيحك الخاصة، مثل أرقام تعريف المستخدمين أو الأسماء الدلالية، أو يمكن تقديمها لك باستخدام childByAutoId.
على سبيل المثال، لنفترض أنّ هناك تطبيق محادثة يتيح للمستخدمين تخزين ملف شخصي أساسي وقائمة جهات اتصال. يقع ملف المستخدم الشخصي العادي في مسار، مثل
/users/$uid. قد يكون لدى المستخدم alovelace إدخال في قاعدة البيانات
يبدو على النحو التالي:
{ "users": { "alovelace": { "name": "Ada Lovelace", "contacts": { "ghopper": true }, }, "ghopper": { "..." }, "eclarke": { "..." } } }
على الرغم من أنّ قاعدة البيانات تستخدم شجرة JSON، يمكن تمثيل البيانات المخزّنة في قاعدة البيانات كأنواع أصلية معيّنة تتوافق مع أنواع JSON المتاحة لمساعدتك في كتابة رمز برمجي أكثر قابلية للصيانة.
أفضل الممارسات المتعلّقة ببنية البيانات
تجنُّب تداخل البيانات
بما أنّ Firebase Realtime Database يسمح بتداخل البيانات حتى 32 مستوى، قد تعتقد أنّ هذه هي البنية التلقائية. ومع ذلك، عند جلب البيانات في موقع في قاعدة البيانات، يمكنك أيضًا استرداد جميع العُقد الفرعية الخاصة به. بالإضافة إلى ذلك، عندما تمنح شخصًا ما إذن الوصول للقراءة أو الكتابة في عقدة في قاعدة البيانات الخاصة بك، فإنك تمنحه أيضًا حق الوصول إلى جميع البيانات الموجودة ضمن تلك العقدة. لذلك، من الأفضل عمليًا الحفاظ على بنية البيانات مسطحة قدر الإمكان.
للاطّلاع على مثال يوضّح سبب عدم جودة البيانات المتداخلة، يمكنك الاطّلاع على البنية المتداخلة المتعددة التالية:
{ // This is a poorly nested data architecture, because iterating the children // of the "chats" node to get a list of conversation titles requires // potentially downloading hundreds of megabytes of messages "chats": { "one": { "title": "Historical Tech Pioneers", "messages": { "m1": { "sender": "ghopper", "message": "Relay malfunction found. Cause: moth." }, "m2": { ... }, // a very long list of messages } }, "two": { "..." } } }
ومع هذا التصميم المتداخل، يصبح تكرار البيانات مشكلة. على سبيل المثال، يتطلب إدراج عناوين محادثات Chat تنزيل شجرة chats بالكامل، بما في ذلك جميع الأعضاء والرسائل، إلى العميل.
تبسيط هياكل البيانات
إذا تم تقسيم البيانات بدلاً من ذلك إلى مسارات منفصلة، وهو ما يُعرف أيضًا باسم إلغاء التسوية، يمكن تنزيلها بكفاءة في طلبات منفصلة، حسب الحاجة. لنفترض أنّ لدينا هذه البنية المسطّحة:
{ // Chats contains only meta info about each conversation // stored under the chats's unique ID "chats": { "one": { "title": "Historical Tech Pioneers", "lastMessage": "ghopper: Relay malfunction found. Cause: moth.", "timestamp": 1459361875666 }, "two": { "..." }, "three": { "..." } }, // Conversation members are easily accessible // and stored by chat conversation ID "members": { // we'll talk about indices like this below "one": { "ghopper": true, "alovelace": true, "eclarke": true }, "two": { "..." }, "three": { "..." } }, // Messages are separate from data we may want to iterate quickly // but still easily paginated and queried, and organized by chat // conversation ID "messages": { "one": { "m1": { "name": "eclarke", "message": "The relay seems to be malfunctioning.", "timestamp": 1459361875337 }, "m2": { "..." }, "m3": { "..." } }, "two": { "..." }, "three": { "..." } } }
أصبح من الممكن الآن تكرار قائمة الغرف من خلال تنزيل بضع بايتات فقط لكل محادثة، واسترجاع البيانات الوصفية بسرعة لإدراج الغرف أو عرضها في واجهة المستخدم. يمكن جلب الرسائل بشكل منفصل وعرضها فور وصولها، ما يتيح لواجهة المستخدم أن تظل سريعة الاستجابة.
إنشاء بيانات قابلة للتوسّع
عند إنشاء التطبيقات، من الأفضل غالبًا تنزيل مجموعة فرعية من القائمة. وهذا شائع بشكل خاص إذا كانت القائمة تحتوي على آلاف السجلات. عندما تكون هذه العلاقة ثابتة وأحادية الاتجاه، يمكنك ببساطة دمج العناصر الفرعية ضمن العنصر الرئيسي.
في بعض الأحيان، تكون هذه العلاقة أكثر ديناميكية، أو قد يكون من الضروري إلغاء تسوية هذه البيانات. في كثير من الأحيان، يمكنك إلغاء تسوية البيانات باستخدام طلب بحث لاسترداد مجموعة فرعية من البيانات، كما هو موضّح في استرداد البيانات.
ولكن حتى هذا قد لا يكون كافيًا. على سبيل المثال، لنفترض أنّ هناك علاقة ثنائية الاتجاه بين المستخدمين والمجموعات. يمكن أن ينتمي المستخدمون إلى مجموعة، وتتألف المجموعات من قائمة بالمستخدمين. عندما يحين وقت تحديد المجموعات التي ينتمي إليها المستخدم، تصبح الأمور معقدة.
ما نحتاجه هو طريقة أنيقة لإدراج المجموعات التي ينتمي إليها المستخدم وجلب البيانات الخاصة بتلك المجموعات فقط. يمكن أن يساعد فهرس المجموعات كثيرًا في هذا الصدد:
// An index to track Ada's memberships { "users": { "alovelace": { "name": "Ada Lovelace", // Index Ada's groups in her profile "groups": { // the value here doesn't matter, just that the key exists "techpioneers": true, "womentechmakers": true } }, // ... }, "groups": { "techpioneers": { "name": "Historical Tech Pioneers", "members": { "alovelace": true, "ghopper": true, "eclarke": true } }, // ... } }
قد تلاحظ أنّ هذا الإجراء يكرّر بعض البيانات من خلال تخزين العلاقة
ضمن سجلّ "علا" وضمن المجموعة. الآن، تتم فهرسة alovelace ضمن مجموعة، ويتم إدراج techpioneers في الملف الشخصي لـ "آية". لذا، لحذف Ada من المجموعة، يجب تحديثها في مكانين.
هذا تكرار ضروري للعلاقات الثنائية. تتيح لك هذه الميزة جلب اشتراكات "سارة" بسرعة وكفاءة، حتى عندما تتسع قائمة المستخدمين أو المجموعات لتشمل الملايين أو عندما تمنع قواعد الأمان في "Realtime Database" الوصول إلى بعض السجلات.
هذا النهج، الذي يعكس البيانات من خلال إدراج أرقام التعريف كمفاتيح وضبط
القيمة على "صحيح"، يجعل التحقّق من المفتاح بسيطًا مثل قراءة
/users/$uid/groups/$group_id والتحقّق مما إذا كان null. الفهرس أسرع وأكثر كفاءة من الاستعلام عن البيانات أو فحصها.