Эта страница была переведа с помощью Cloud Translation API.
Switch to English

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

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

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

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

Все данные базы данных Firebase Realtime хранятся в виде объектов JSON. Вы можете думать о базе данных как о дереве JSON, размещенном в облаке. В отличие от базы данных SQL, здесь нет таблиц или записей. Когда вы добавляете данные в дерево JSON, оно становится узлом в существующей структуре JSON со связанным ключом. Вы можете предоставить свои собственные ключи, такие как идентификаторы пользователей или семантические имена, или они могут быть предоставлены вам с push() метода 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, даже когда список пользователей или групп масштабируется до миллионов или когда правила безопасности базы данных в реальном времени запрещают доступ к некоторым записям.

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

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