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