建立資料庫結構

事前準備

如要使用 Realtime Database,請先完成下列步驟:

  • 註冊 Unity 專案並設定使用 Firebase。

    • 如果 Unity 專案已使用 Firebase,則專案已註冊並設定 Firebase。

    • 如果沒有 Unity 專案,可以下載範例應用程式

  • Firebase Unity SDK (具體來說是 FirebaseDatabase.unitypackage) 新增至 Unity 專案。

請注意,將 Firebase 新增至 Unity 專案時,您需要在 Firebase 控制台和開啟的 Unity 專案中執行工作 (例如從控制台下載 Firebase 設定檔,然後移至 Unity 專案)。

將資料結構化

本指南將介紹資料架構的一些重要概念,以及在 Firebase Realtime Database 中建構 JSON 資料的最佳做法。

建構結構完善的資料庫需要相當多的預先規劃。最重要的是,您需要規劃如何儲存資料,以及日後如何擷取資料,盡可能簡化這個程序。

資料結構:JSON 樹狀結構

所有 Firebase Realtime Database 資料都會儲存為 JSON 物件,您可以將資料庫想像成雲端託管 JSON 樹狀結構。與 SQL 資料庫不同的是,這個資料庫中沒有表格或記錄。將資料新增至 JSON 樹狀結構時,資料會在現有 JSON 結構中變成一個節點,且包含關聯的金鑰。您可以提供自己的金鑰,例如使用者 ID 或語意名稱,也可以使用 Push() 方法取得金鑰。

舉例來說,假設某個即時通訊應用程式允許使用者儲存基本個人資料和聯絡人清單,一般使用者設定檔位於路徑,例如 /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 一樣簡單。與查詢或掃描資料相比,索引速度更快,效率也高出許多。

後續步驟