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