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

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

В этом руководстве рассматриваются некоторые ключевые концепции архитектуры данных и передовые методы структурирования данных 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, даже когда список пользователей или групп исчисляется миллионами или когда правила безопасности базы данных реального времени предотвращают доступ к некоторым записям.

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

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