يتناول هذا الدليل بعض المفاهيم الأساسية في بنية البيانات وأفضل الممارسات لتنظيم بيانات JSON في Firebase Realtime Database.
يتطلّب إنشاء قاعدة بيانات منظَّمة بشكل صحيح قدرًا كبيرًا من التفكير المسبق. والأهم من ذلك، عليك التخطيط لكيفية حفظ البيانات و استردادها لاحقًا لتسهيل هذه العملية قدر الإمكان.
بنية البيانات: هي شجرة JSON
يتم تخزين جميع بيانات Firebase Realtime Database كعناصر JSON. يمكنك اعتبار
قاعدة البيانات شجرة JSON مستضافة على السحابة الإلكترونية. على عكس قاعدة بيانات SQL، لا تتضمّن قاعدة بيانات NoSQL
جداول أو سجلّات. عند إضافة بيانات إلى شجرة JSON، تصبح هذه البيانات عقدة في
بنية JSON الحالية مع مفتاح مرتبط بها. يمكنك تقديم مفاتيح خاصة بك،
مثل أرقام تعريف المستخدمين أو الأسماء الدلالية، أو يمكن أن يتم تقديمها لك باستخدام
طلب POST
.
إذا أنشأت مفاتيحك الخاصة، يجب أن تكون بترميز 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": { ... } } }
باستخدام هذا التصميم المُدمَج، يصبح تكرار البيانات مشكلة. على سبيل المثال، يتطلب إدراج عناوين محادثات المحادثة تنزيل 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": { ... } } }
أصبح من الممكن الآن التنقّل في قائمة الغرف من خلال تنزيل بضع بايتات فقط لكل محادثة، ما يتيح جلب البيانات الوصفية بسرعة لعرض الغرف في واجهة مستخدِم أو إدراجها. يمكن جلب الرسائل بشكل منفصل وعرضها فور وصولها، مما يسمح لواجهة المستخدم بالبقاء سريعة الاستجابة.
إنشاء بيانات قابلة للتوسّع
عند إنشاء التطبيقات، من الأفضل غالبًا تنزيل مجموعة فرعية من قائمة. ويحدث ذلك بشكل شائع إذا كانت القائمة تحتوي على آلاف السجلات. عندما تكون هذه العلاقة ثابتة واتجاهية واحدة، يمكنك ببساطة تداخل العناصر الفرعية ضمن العنصر الرئيسي.
في بعض الأحيان، تكون هذه العلاقة أكثر ديناميكية، أو قد يكون من الضروري إزالة تسوية هذه البيانات. في كثير من الأحيان، يمكنك إزالة التحويل العادي للبيانات باستخدام استعلام لاسترداد مجموعة فرعية من البيانات، كما هو موضّح في مقالة استرداد البيانات.
ولكن قد لا يكون ذلك كافيًا. على سبيل المثال، ضع في اعتبارك علاقة ثنائية بين المستخدمين والمجموعات. يمكن أن ينتمي المستخدمون إلى مجموعة، وتتألف المجموعات من قائمة بالمستخدمين. عندما يحين وقت تحديد المجموعات التي ينتمي إليها المستخدم، تصبح الأمور معقدة.
ما عليك سوى استخدام طريقة أنيقة لعرض المجموعات التي ينتمي إليها المستخدم و retrieving فقط بيانات هذه المجموعات. يمكن أن يساعدك فهرس المجموعات في حلّ العديد من المشاكل:
// 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 الشخصي. وبالتالي، لحذف Ada
من المجموعة، يجب تعديلها في مكانَين.
ويعتبر هذا التكرار ضروريًا للعلاقات في الاتجاهين. يتيح لك الإجراء retrieving استرداد عضويات Ada بسرعة وكفاءة، حتى عندما تتوسع قائمة المستخدمين أو المجموعات إلى ملايين المستخدمين أو عندما تمنع Realtime Database قواعد الأمان الوصول إلى بعض السجلات.
بهذه الطريقة، يتم عكس البيانات من خلال إدراج أرقام التعريف كمفاتيح وضبط
القيمة على "صحيح"، يجعل عملية البحث عن مفتاح أمرًا بسيطًا مثل قراءة /users/$uid/groups/$group_id
والتحقق مما إذا كان null
. إنّ الفهرس أسرع
وأكثر فعالية بكثير من طلب البيانات أو فحصها.