Zanim zaczniesz
Zanim zaczniesz korzystać z Realtime Database, musisz:
zarejestrować projekt w Unity i skonfigurować go pod kątem korzystania z Firebase.
Jeśli Twój projekt w Unity korzysta już z Firebase, jest on już zarejestrowany i skonfigurowany pod kątem Firebase.
Jeśli nie masz projektu w Unity, możesz pobrać przykładową aplikację.
dodać pakiet Firebase Unity SDK (a konkretnie
FirebaseDatabase.unitypackage) do projektu w Unity.
Pamiętaj, że dodanie Firebase do projektu w Unity wymaga wykonania zadań zarówno w Firebase konsoli jak i w otwartym projekcie w Unity (np. pobierasz pliki konfiguracyjne Firebase z konsoli, a następnie przenosisz je do projektu w Unity).
Strukturyzowanie danych
W tym przewodniku omawiamy niektóre kluczowe koncepcje architektury danych i sprawdzone metody strukturyzowania danych JSON w bazie danych Firebase Realtime Database.
Zbudowanie prawidłowo skonstruowanej bazy danych wymaga sporo przemyślenia. Przede wszystkim musisz zaplanować, jak dane będą zapisywane i później pobierane, aby ten proces był jak najprostszy.
Struktura danych: drzewo JSON
Wszystkie dane są przechowywane jako obiekty JSON.Firebase Realtime Database Bazę danych można traktować jako hostowane w chmurze drzewo JSON. W przeciwieństwie do bazy danych SQL nie ma w niej tabel ani rekordów. Gdy dodajesz dane do drzewa JSON, stają się one węzłem w istniejącej strukturze JSON z powiązanym kluczem. Możesz podać własne klucze, np. identyfikatory użytkowników lub nazwy semantyczne, albo klucze mogą zostać podane za Ciebie za pomocą metody Push().
Rozważmy na przykład aplikację do czatu, która umożliwia użytkownikom przechowywanie podstawowego profilu i listy kontaktów. Typowy profil użytkownika znajduje się w ścieżce, np. /users/$uid. Użytkownik alovelace może mieć wpis w bazie danych, który wygląda mniej więcej tak:
{ "users": { "alovelace": { "name": "Ada Lovelace", "contacts": { "ghopper": true }, }, "ghopper": { "..." }, "eclarke": { "..." } } }
Chociaż baza danych używa drzewa JSON, dane w niej przechowywane można przedstawiać jako określone typy natywne, które odpowiadają dostępnym typom JSON. Ułatwia to pisanie kodu, który jest łatwiejszy w utrzymaniu.
Sprawdzone metody tworzenia struktury danych
Unikaj zagnieżdżania danych
Ponieważ Firebase Realtime Database umożliwia zagnieżdżanie danych do 32 poziomów, możesz uznać, że powinna to być domyślna struktura. Gdy jednak pobierasz dane z lokalizacji w bazie danych, pobierasz też wszystkie jej węzły podrzędne. Ponadto, gdy przyznajesz komuś dostęp do odczytu lub zapisu w węźle w bazie danych, przyznajesz mu też dostęp do wszystkich danych znajdujących się pod tym węzłem. Dlatego w praktyce najlepiej jest utrzymywać strukturę danych jak najbardziej płaską.
Aby zobaczyć przykład, dlaczego zagnieżdżone dane są złe, rozważmy tę wielokrotnie zagnieżdżoną strukturę:
{ // 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": { "..." } } }
W przypadku tej zagnieżdżonej struktury iterowanie po danych staje się problematyczne. Na przykład, aby wyświetlić tytuły rozmów na czacie, trzeba pobrać na klienta całe drzewo chats, w tym wszystkich członków i wiadomości.
Spłaszczanie struktur danych
Jeśli dane są podzielone na osobne ścieżki, co nazywamy denormalizacją, można je efektywnie pobierać w osobnych wywołaniach, gdy są potrzebne. Rozważmy tę spłaszczoną strukturę:
{ // 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": { "..." } } }
Teraz można iterować po liście pokoi, pobierając tylko kilka bajtów na rozmowę, co pozwala szybko pobierać metadane do wyświetlania pokoi w interfejsie. Wiadomości można pobierać osobno i wyświetlać w miarę ich przychodzenia, co pozwala zachować responsywność i szybkość interfejsu.
Tworzenie danych, które można skalować
Podczas tworzenia aplikacji często lepiej jest pobrać podzbiór listy. Jest to szczególnie powszechne, gdy lista zawiera tysiące rekordów. Gdy ta relacja jest statyczna i jednokierunkowa, możesz po prostu zagnieździć obiekty podrzędne pod obiektem nadrzędnym.
Czasami ta relacja jest bardziej dynamiczna lub może być konieczne denormalizowanie tych danych. W wielu przypadkach można denormalizować dane za pomocą zapytania aby pobrać podzbiór danych, jak opisano w Pobieranie danych.
Ale nawet to może być niewystarczające. Rozważmy na przykład dwukierunkową relację między użytkownikami a grupami. Użytkownicy mogą należeć do grupy, a grupy składają się z listy użytkowników. Gdy trzeba zdecydować, do których grup należy użytkownik, sytuacja się komplikuje.
Potrzebny jest elegancki sposób na wyświetlenie listy grup, do których należy użytkownik, i pobranie tylko danych tych grup. W tym przypadku bardzo pomocny może być indeks grup:
// 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 } }, // ... } }
Możesz zauważyć, że powoduje to duplikowanie niektórych danych przez przechowywanie relacji zarówno w rekordzie Ady, jak i w grupie. Teraz alovelace jest indeksowany w grupie, a techpioneers jest wymieniony w profilu Ady. Aby usunąć Adę z grupy, trzeba zaktualizować 2 miejsca.
Jest to niezbędna nadmiarowość w przypadku relacji dwukierunkowych. Umożliwia to szybkie i efektywne pobieranie członkostwa Ady, nawet gdy lista użytkowników lub grup rozrośnie się do milionów albo gdy Realtime Database reguły bezpieczeństwa uniemożliwiają dostęp do niektórych rekordów.
To podejście, polegające na odwróceniu danych przez wyświetlenie identyfikatorów jako kluczy i ustawienie wartości na true, sprawia, że sprawdzanie klucza jest tak proste jak odczytanie /users/$uid/groups/$group_id i sprawdzenie, czy jest on null. Indeks jest szybszy i znacznie bardziej wydajny niż wysyłanie zapytań do danych lub ich skanowanie.