Структурируйте свою базу данных

В этом руководстве рассматриваются некоторые ключевые концепции архитектуры данных и передовые методы структурирования данных JSON в вашей базе данных Firebase Realtime.

Создание правильно структурированной базы данных требует некоторой предусмотрительности. Что наиболее важно, вам необходимо спланировать, как данные будут сохраняться, а затем извлекаться, чтобы максимально упростить этот процесс.

Как структурированы данные: это дерево JSON

Все данные базы данных Firebase Realtime хранятся в виде объектов 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 позволяет размещать данные на глубине до 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 указано в профиле Ады. Итак, чтобы удалить Аду из группы, ее нужно обновить в двух местах.

Это необходимая избыточность для двусторонних отношений. Это позволяет вам быстро и эффективно получать членство в Ada, даже если список пользователей или групп исчисляется миллионами или когда правила безопасности базы данных реального времени предотвращают доступ к некоторым записям.

Такой подход, переворачивая данные путем перечисления идентификаторов в качестве ключей и установите значение истинно, делает проверки ключа так просто , как чтение /users/$uid/groups/$group_id и проверок , если это null . Индекс работает быстрее и намного эффективнее, чем запрос или сканирование данных.

Следующие шаги