建立資料庫

本指南說明資料架構的幾個重要概念,以及建構 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 一樣簡單。索引速度較快,且比查詢或掃描資料更有效率。

後續步驟