يتناول هذا الدليل بعض المفاهيم الأساسية في بنية البيانات وأفضل الممارسات لتنظيم بيانات JSON في Firebase Realtime Database.
يتطلّب إنشاء قاعدة بيانات منظَّمة بشكل صحيح الكثير من التفكير المسبق. والأهم من ذلك، عليك التخطيط لكيفية حفظ البيانات واسترجاعها لاحقًا لتسهيل هذه العملية قدر الإمكان.
طريقة تنظيم البيانات: هي عبارة عن شجرة JSON
يتم تخزين جميع بيانات Firebase Realtime Database كعناصر JSON. يمكنك اعتبار قاعدة البيانات بمثابة شجرة JSON مستضافة على السحابة الإلكترونية. وعلى عكس قاعدة بيانات SQL، لا تتضمّن هذه القاعدة جداول أو سجلات. عند إضافة بيانات إلى شجرة JSON، تصبح عقدة في بنية JSON الحالية مع مفتاح مرتبط. يمكنك تقديم مفاتيحك الخاصة، مثل معرّفات المستخدمين أو الأسماء الدلالية، أو يمكن توفيرها لك باستخدام الطريقة push()
.
إذا أنشأت مفاتيحك الخاصة، يجب أن تكون بترميز UTF-8، ويمكن أن يصل حجمها إلى 768 بايت كحد أقصى، ويجب ألا تحتوي على .
أو $
أو #
أو [
أو ]
أو /
أو أحرف ASCII للتحكّم من 0 إلى 31 أو 127. لا يمكنك أيضًا استخدام أحرف التحكّم ASCII في القيم نفسها.
على سبيل المثال، لنفترض أنّ هناك تطبيق دردشة يتيح للمستخدمين تخزين ملف شخصي أساسي وقائمة جهات اتصال. يقع ملف المستخدم النموذجي في مسار، مثل
/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 } }, // ... } }
قد تلاحظ أنّ هذا الإجراء يؤدي إلى تكرار بعض البيانات من خلال تخزين العلاقة ضمن سجلّ Ada وضمن المجموعة. تمت فهرسة alovelace
ضمن مجموعة، وتم إدراج techpioneers
في الملف الشخصي الخاص بـ "آدا". لذا، لحذف Ada من المجموعة، يجب تعديلها في مكانَين.
هذه الخطوة ضرورية لتجنُّب التكرار في العلاقات الثنائية. يتيح لك هذا الإجراء استرداد عضويات Ada بسرعة وفعالية، حتى عندما تتوسّع قائمة المستخدمين أو المجموعات لتشمل الملايين أو عندما تمنع قواعد الأمان في Realtime Database الوصول إلى بعض السجلات.
تتيح هذه الطريقة، التي تعكس البيانات من خلال إدراج المعرّفات كمفاتيح وتعيين القيمة على "صحيح"، التحقّق من المفتاح بسهولة من خلال قراءة /users/$uid/groups/$group_id
والتحقّق مما إذا كانت القيمة null
. الفهرس أسرع وأكثر كفاءة من البحث عن البيانات أو فحصها.