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