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

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

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

Building a properly structured database requires quite a bit of forethought. Most importantly, you need to plan for how data is going to be saved and later retrieved to make that process as easy as possible.

How data is structured: it's a JSON tree

Все данные Firebase Realtime Database хранятся в виде объектов JSON. Базу данных можно рассматривать как облачное дерево JSON. В отличие от базы данных SQL, здесь нет таблиц или записей. При добавлении данных в дерево JSON они становятся узлом в существующей структуре JSON с соответствующим ключом. Вы можете указать свои собственные ключи, такие как идентификаторы пользователей или семантические имена, или они могут быть предоставлены автоматически с помощью метода Push() .

Например, рассмотрим приложение для чата, которое позволяет пользователям хранить базовый профиль и список контактов. Типичный профиль пользователя находится по пути, например, /users/$uid . У пользователя alovelace может быть запись в базе данных, которая выглядит примерно так:

{
  "users": {
    "alovelace": {
      "name": "Ada Lovelace",
      "contacts": { "ghopper": true },
    },
    "ghopper": { "..." },
    "eclarke": { "..." }
  }
}

Хотя база данных использует JSON-дерево, данные, хранящиеся в базе данных, могут быть представлены в виде определенных собственных типов, соответствующих доступным типам JSON, что помогает писать более поддерживаемый код.

Best practices for data structure

Avoid nesting data

Поскольку 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 , включая всех участников и сообщения.

Flatten data structures

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

{
  // 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": { "..." }
  }
}

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

Create data that scales

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

Иногда эта взаимосвязь более динамична, или может потребоваться денормализация данных. Во многих случаях денормализацию данных можно выполнить с помощью запроса для получения подмножества данных, как описано в разделе «Получение данных» .

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

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

// 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 . Индекс работает быстрее и значительно эффективнее, чем запросы или сканирование данных.

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