يغطّي هذا الدليل بعض المفاهيم الرئيسية في بنية البيانات وأفضل الممارسات لتنظيم بيانات JSON في قاعدة بيانات Firebase في الوقت الفعلي.
يتطلّب إنشاء قاعدة بيانات منظَّمة بشكل صحيح قدرًا كبيرًا من التفكير المسبق. الأهم من ذلك هو التخطيط لكيفية حفظ البيانات واستردادها لاحقًا لتسهيل هذه العملية قدر الإمكان.
كيفية تنظيم البيانات: هي عبارة عن شجرة JSON
يتم تخزين جميع بيانات قاعدة بيانات Firebase في الوقت الفعلي كعناصر JSON. يمكنك اعتبار قاعدة البيانات شجرة JSON مستضافة على السحابة الإلكترونية. على عكس قاعدة بيانات SQL، لا توجد جداول أو سجلات. عند إضافة بيانات إلى شجرة JSON، تصبح عقدة في بنية JSON الحالية مع مفتاح مرتبط. يمكنك تقديم مفاتيحك الخاصة، مثل أرقام تعريف المستخدمين أو الأسماء الدلالية، أو يمكن توفيرها لك باستخدام push().
على سبيل المثال، لنفترض أنّ لديك تطبيق محادثة يتيح للمستخدمين تخزين ملف شخصي أساسي وقائمة جهات اتصال. يقع الملف الشخصي العادي للمستخدم في مسار، مثل /users/$uid. قد يكون لدى المستخدم alovelace إدخال في قاعدة البيانات يبدو على النحو التالي:
{
"users": {
"alovelace": {
"name": "Ada Lovelace",
"contacts": { "ghopper": true },
},
"ghopper": { ... },
"eclarke": { ... }
}
}
على الرغم من أنّ قاعدة البيانات تستخدم شجرة JSON، يمكن تمثيل البيانات المخزّنة في قاعدة البيانات كأنواع أصلية معيّنة تتوافق مع أنواع JSON المتاحة لمساعدتك في كتابة رمز أكثر قابلية للصيانة.
أفضل الممارسات لبنية البيانات
تجنُّب البيانات المتداخلة
بما أنّ قاعدة بيانات Firebase في الوقت الفعلي تسمح بتداخل البيانات حتى 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
}
},
...
}
}
قد تلاحظ أنّ هذا يكرّر بعض البيانات من خلال تخزين العلاقة ضِمن سجلّ Ada وضِمن المجموعة. تم الآن فهرسة alovelace ضِمن مجموعة، وتم إدراج techpioneers في الملف الشخصي لـ Ada. لذلك، لحذف Ada من المجموعة، يجب تعديلها في مكانَين.
هذه عملية تكرار ضرورية للعلاقات الثنائية الاتجاه. تسمح لك بجلب عضويات Ada بسرعة وكفاءة، حتى عندما يتوسّع عدد المستخدمين أو المجموعات إلى الملايين أو عندما تمنع قواعد الأمان في قاعدة بيانات الوقت الفعلي الوصول إلى بعض السجلات.
يؤدي هذا النهج، الذي يعكس البيانات من خلال إدراج أرقام التعريف كمفاتيح وضبط القيمة على "صحيح"، إلى تسهيل التحقّق من المفتاح من خلال قراءة /users/$uid/groups/$group_id والتحقّق مما إذا كانت القيمة null. الفهرس أسرع وأكثر كفاءة من طلب البحث عن البيانات أو فحصها.