تنظيم قاعدة البيانات

يتناول هذا الدليل بعض المفاهيم الأساسية في بنية البيانات وأفضل الممارسات لتنظيم بيانات JSON في قاعدة بيانات Firebase في الوقت الفعلي.

يتطلب إنشاء قاعدة بيانات هيكلة بشكل صحيح الكثير من التفكير. الأهم من ذلك، أنك تحتاج إلى التخطيط لكيفية حفظ البيانات واستردادها لاحقًا لجعل هذه العملية سهلة قدر الإمكان.

طريقة تنظيم البيانات: عبارة عن شجرة JSON

يتم تخزين جميع بيانات قاعدة بيانات Firebase في الوقت الفعلي كعناصر 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 في الوقت الفعلي تسمح بدمج البيانات بما يصل إلى 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 بسرعة وكفاءة، حتى عندما تتزايد قائمة المستخدمين أو المجموعات إلى الملايين أو عندما تمنع قواعد أمان قاعدة البيانات في الوقت الفعلي الوصول إلى بعض السجلات.

ومن خلال هذه الطريقة، يتم عكس البيانات من خلال إدراج أرقام التعريف كمفاتيح وضبط القيمة على "صحيح"، يجعل عملية البحث عن مفتاح أمرًا بسيطًا مثل قراءة /users/$uid/groups/$group_id والتحقق مما إذا كان null. يكون الفهرس أسرع وأكثر كفاءة بكثير من الاستعلام عن البيانات أو مسحها ضوئيًا.

الخطوات التالية