يتناول هذا الدليل بعض المفاهيم الأساسية في بنية البيانات وأفضل الممارسات لتنظيم بيانات 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": { "..." } } }
مع هذا التصميم المتداخل، يصبح تكرار البيانات أمرًا صعبًا. على سبيل المثال، يتطلّب عرض عناوين محادثات الدردشة تنزيل شجرة 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. يكون الفهرس أسرع وأكثر فعالية من البحث عن البيانات أو فحصها.