本指南涵蓋了數據架構中的一些關鍵概念以及在 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
一樣簡單。索引比查詢或掃描數據更快、更高效。