本指南將介紹資料架構中的部分重要概念,以及在 Firebase Realtime Database 中建構 JSON 資料的最佳做法。
建構結構良好的資料庫需要相當多的前置作業。最重要的是,您需要規劃資料的儲存和稍後擷取方式,盡可能簡化這項程序。
資料的結構:JSON 樹狀結構
所有 Firebase Realtime Database 資料都會儲存為 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 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
樹狀結構 (包括所有成員和訊息) 下載到用戶端。
壓平合併資料結構
如果資料分割為不同的路徑 (也稱為去標準化),則可視需要透過個別呼叫有效下載。請考慮這個扁平化結構:
{ // 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,必須在兩個地方更新。
這是雙向關係的必要備援機制。即使使用者或群組清單規模達到數百萬,或 Realtime Database 安全性規則阻止存取部分記錄,您仍可快速且有效率地擷取 Ada 的會員資格。
這個方法會將 ID 列為鍵並將值設為 true,藉此反轉資料,讓檢查鍵的操作變得簡單,就像讀取 /users/$uid/groups/$group_id
並檢查是否為 null
一樣。索引比查詢或掃描資料更快、更有效率。