Catch up on everything announced at Firebase Summit, and learn how Firebase can help you accelerate app development and run your app with confidence. Learn More

Como estruturar dados com o Firebase Realtime Database para C++

Mantenha tudo organizado com as coleções Salve e categorize o conteúdo com base nas suas preferências.

Estruturando dados

Este guia aborda alguns dos principais conceitos de arquitetura de dados e práticas recomendadas para estruturar os dados JSON no Firebase Realtime Database.

Construir um banco de dados estruturado corretamente requer um pouco de premeditação. Mais importante ainda, você precisa planejar como os dados serão salvos e recuperados posteriormente para tornar esse processo o mais fácil possível.

Como os dados são estruturados: é uma árvore JSON

Todos os dados do Firebase Realtime Database são armazenados como objetos JSON. Você pode pensar no banco de dados como uma árvore JSON hospedada na nuvem. Ao contrário de um banco de dados SQL, não há tabelas ou registros. Quando você adiciona dados à árvore JSON, ela se torna um nó na estrutura JSON existente com uma chave associada. Você pode fornecer suas próprias chaves, como IDs de usuário ou nomes semânticos, ou elas podem ser fornecidas a você usando o método Push() .

Se você criar suas próprias chaves, elas devem ser codificadas em UTF-8, podem ter no máximo 768 bytes e não podem conter arquivos . , $ , # , [ , ] , / ou caracteres de controle ASCII 0-31 ou 127. Você também não pode usar caracteres de controle ASCII nos próprios valores.

Por exemplo, considere um aplicativo de bate-papo que permite aos usuários armazenar um perfil básico e uma lista de contatos. Um perfil de usuário típico está localizado em um caminho, como /users/$uid . O usuário alovelace pode ter uma entrada de banco de dados parecida com esta:

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

Embora o banco de dados use uma árvore JSON, os dados armazenados no banco de dados podem ser representados como determinados tipos nativos que correspondem aos tipos JSON disponíveis para ajudá-lo a escrever um código mais sustentável.

Práticas recomendadas para estrutura de dados

Evite aninhar dados

Como o Firebase Realtime Database permite aninhar dados com até 32 níveis de profundidade, você pode ficar tentado a pensar que essa deve ser a estrutura padrão. No entanto, ao buscar dados em um local do banco de dados, você também recupera todos os nós filhos. Além disso, quando você concede a alguém acesso de leitura ou gravação em um nó em seu banco de dados, você também concede acesso a todos os dados desse nó. Portanto, na prática, é melhor manter sua estrutura de dados o mais plana possível.

Para obter um exemplo de por que os dados aninhados são ruins, considere a seguinte estrutura aninhada de forma múltipla:

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

Com esse design aninhado, iterar pelos dados se torna problemático. Por exemplo, listar os títulos das conversas de bate-papo exige que toda a árvore de bate- chats , incluindo todos os membros e mensagens, seja baixada para o cliente.

Achatar estruturas de dados

Se os dados forem divididos em caminhos separados, também chamados de desnormalização, eles poderão ser baixados com eficiência em chamadas separadas, conforme necessário. Considere esta estrutura achatada:

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

Agora é possível percorrer a lista de salas baixando apenas alguns bytes por conversa, buscando rapidamente metadados para listagem ou exibindo salas em uma interface do usuário. As mensagens podem ser buscadas separadamente e exibidas à medida que chegam, permitindo que a interface do usuário permaneça responsiva e rápida.

Crie dados que escalam

Ao criar aplicativos, geralmente é melhor baixar um subconjunto de uma lista. Isso é particularmente comum se a lista contiver milhares de registros. Quando esse relacionamento é estático e unidirecional, você pode simplesmente aninhar os objetos filho sob o pai.

Às vezes, esse relacionamento é mais dinâmico, ou pode ser necessário desnormalizar esses dados. Muitas vezes você pode desnormalizar os dados usando uma consulta para recuperar um subconjunto dos dados, conforme discutido em Recuperar Dados .

Mas mesmo isso pode ser insuficiente. Considere, por exemplo, um relacionamento bidirecional entre usuários e grupos. Os usuários podem pertencer a um grupo e os grupos compreendem uma lista de usuários. Quando chega a hora de decidir a quais grupos um usuário pertence, as coisas ficam complicadas.

O que é necessário é uma maneira elegante de listar os grupos aos quais um usuário pertence e buscar apenas dados para esses grupos. Um índice de grupos pode ajudar bastante aqui:

// 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
      }
    },
    ...
  }
}

Você pode notar que isso duplica alguns dados armazenando o relacionamento no registro de Ada e no grupo. Agora alovelace está indexado em um grupo, e techpioneers está listado no perfil de Ada. Portanto, para excluir Ada do grupo, ela deve ser atualizada em dois lugares.

Esta é uma redundância necessária para relacionamentos bidirecionais. Ele permite que você busque de forma rápida e eficiente as associações de Ada, mesmo quando a lista de usuários ou grupos chega a milhões ou quando as regras de segurança do Realtime Database impedem o acesso a alguns dos registros.

Essa abordagem, invertendo os dados listando os IDs como chaves e definindo o valor como true, torna a verificação de uma chave tão simples quanto ler /users/$uid/groups/$group_id e verificar se é null . O índice é mais rápido e muito mais eficiente do que consultar ou verificar os dados.

Próximos passos