هيكلة قاعدة البيانات

يغطّي هذا الدليل بعض المفاهيم الرئيسية في بنية البيانات وأفضل الممارسات لتنظيم بيانات JSON في Firebase Realtime Database.

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

كيفية تنظيم البيانات: هي شجرة JSON

يتم تخزين جميع بيانات Firebase Realtime Database ككائنات JSON. يمكنك اعتبار قاعدة البيانات كشجرة JSON مستضافة على السحابة الإلكترونية. على عكس قاعدة بيانات SQL، لا توجد جداول أو سجلات. عند إضافة بيانات إلى شجرة JSON، تصبح عقدة في بنية JSON الحالية مع مفتاح مرتبط. يمكنك تقديم مفاتيحك الخاصة، مثل معرّفات المستخدمين أو الأسماء الدلالية، أو يمكن توفيرها لك باستخدام push().

على سبيل المثال، لنفترض أنّ لديك تطبيق محادثة يتيح للمستخدمين تخزين ملف شخصي أساسي وقائمة جهات اتصال. يقع الملف الشخصي العادي للمستخدم في مسار، مثل /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 في الملف الشخصي لـ "آدا". لذلك، لحذف "آدا" من المجموعة، يجب تعديلها في مكانَين.

هذه عملية تكرار ضرورية للعلاقات الثنائية الاتجاه. تسمح لك بـ جلب عضويات "آدا" بسرعة وكفاءة، حتى عندما يتوسّع عدد المستخدمين أو المجموعات إلى الملايين أو عندما تمنع قواعد الأمان في Realtime Databaseالوصول إلى بعض السجلات.

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

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