Структурирование данных с помощью базы данных Firebase Realtime для C++

Структурирование данных

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

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

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

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

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

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

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