建立資料庫

本指南說明資料架構的一些重要概念,以及建構 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。相較於查詢或掃描資料,索引速度更快,也更有效率。

後續步驟