获取我们在 Firebase 峰会上发布的所有信息,了解 Firebase 可如何帮助您加快应用开发速度并满怀信心地运行应用。了解详情

构建您的数据库

使用集合让一切井井有条 根据您的偏好保存内容并对其进行分类。

本指南涵盖了数据架构中的一些关键概念以及在 Firebase 实时数据库中构建 JSON 数据的最佳实践。

构建一个结构合理的数据库需要相当多的深谋远虑。最重要的是,您需要计划如何保存和稍后检索数据,以使该过程尽可能简单。

数据的结构:它是一个 JSON 树

所有 Firebase 实时数据库数据都存储为 JSON 对象。您可以将数据库视为云托管的 JSON 树。与 SQL 数据库不同,没有表或记录。当您将数据添加到 JSON 树时,它会成为现有 JSON 结构中具有关联键的节点。您可以提供自己的密钥,例如用户 ID 或语义名称,也可以使用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 实时数据库允许嵌套数据最多 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": { ... }
  }
}

现在可以通过每次对话仅下载几个字节来遍历房间列表,快速获取元数据以在 UI 中列出或显示房间。消息可以单独获取并在它们到达时显示,从而使 UI 保持响应速度和速度。

创建可扩展的数据

在构建应用程序时,通常最好下载列表的子集。如果列表包含数千条记录,这尤其常见。当这种关系是静态且单向的时,您可以简单地将子对象嵌套在父对象之下。

有时,这种关系更加动态,或者可能需要对这些数据进行非规范化。很多时候,您可以通过使用查询来检索数据的子集来对数据进行非规范化,如排序和过滤数据中所述。

但即使这样也可能是不够的。例如,考虑用户和组之间的双向关系。用户可以属于一个组,组包含一个用户列表。当需要决定用户属于哪些组时,事情会变得复杂。

需要一种优雅的方式来列出用户所属的组并仅获取这些组的数据。组索引在这里可以提供很大帮助:

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

您可能会注意到,通过将关系存储在 Ada 的记录和组下,这会复制一些数据。现在alovelace被索引在一个组下, techpioneers被列在 Ada 的个人资料中。因此,要从组中删除 Ada,必须在两个位置进行更新。

这是双向关系的必要冗余。它允许您快速有效地获取 Ada 的成员资格,即使用户或组的列表扩展到数百万或实时数据库安全规则阻止访问某些记录时也是如此。

这种方法通过将 ID 列为键并将值设置为 true 来反转数据,使得检查键就像读取/users/$uid/groups/$group_id并检查它是否为null一样简单。索引比查询或扫描数据更快,效率更高。

下一步