Zorganizuj swoją bazę danych

W tym przewodniku omówiono niektóre kluczowe pojęcia związane z architekturą danych oraz sprawdzone metody tworzenia struktury danych JSON w bazie danych czasu rzeczywistego Firebase.

Zbudowanie odpowiednio ustrukturyzowanej bazy danych wymaga sporego namysłu. Co najważniejsze, musisz zaplanować sposób zapisywania i późniejszego pobierania danych, aby ten proces był jak najłatwiejszy.

Struktura danych: to drzewo JSON

Wszystkie dane bazy danych czasu rzeczywistego Firebase są przechowywane jako obiekty JSON. Możesz myśleć o bazie danych jako o drzewie JSON hostowanym w chmurze. W przeciwieństwie do bazy danych SQL nie ma tabel ani rekordów. Po dodaniu danych do drzewa JSON stają się one węzłem w istniejącej strukturze JSON ze skojarzonym kluczem. Możesz podać własne klucze, takie jak identyfikatory użytkowników lub nazwy semantyczne, lub możesz je podać za pomocą funkcji push() .

Jeśli tworzysz własne klucze, muszą być zakodowane w UTF-8, mogą mieć maksymalnie 768 bajtów i nie mogą zawierać . , $ , # , [ , ] , / lub znaków sterujących ASCII 0-31 lub 127. Nie można również używać znaków sterujących ASCII w samych wartościach.

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ę na ścieżce, takiej jak /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 przechowywane w bazie danych mogą być reprezentowane jako pewne typy natywne, które odpowiadają dostępnym typom JSON, aby ułatwić pisanie kodu, który można łatwiej konserwować.

Najlepsze praktyki dotyczące struktury danych

Unikaj zagnieżdżania danych

Ponieważ baza danych czasu rzeczywistego Firebase pozwala na zagnieżdżanie danych do 32 poziomów, możesz pokusić się o stwierdzenie, że powinna to być struktura domyślna. Jednak gdy pobierasz dane z lokalizacji w bazie danych, pobierasz również wszystkie jej węzły podrzędne. Ponadto, gdy przyznajesz komuś dostęp do odczytu lub zapisu w węźle w Twojej bazie danych, przyznajesz mu również dostęp do wszystkich danych w tym węźle. Dlatego w praktyce najlepiej jest zachować jak najbardziej płaską strukturę danych.

Aby zapoznać się z przykładem, dlaczego dane zagnieżdżone są złe, rozważ następującą strukturę wielokrotnie zagnieżdżoną:

{
  // 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 tego zagnieżdżonego projektu iteracja danych staje się problematyczna. Na przykład wyświetlanie tytułów rozmów na czacie wymaga pobrania do klienta całego drzewa chats , w tym wszystkich członków i wiadomości.

Spłaszcz struktury danych

Jeśli zamiast tego dane zostaną podzielone na oddzielne ścieżki, zwane również denormalizacją, można je skutecznie pobrać w osobnych wywołaniach, zgodnie z potrzebami. Rozważ 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 przeglądać listę pokoi, pobierając tylko kilka bajtów na rozmowę, szybko pobierając metadane do wyświetlania listy lub wyświetlania pokoi w interfejsie użytkownika. Wiadomości można pobierać osobno i wyświetlać po ich otrzymaniu, dzięki czemu interfejs użytkownika pozostaje responsywny i szybki.

Twórz dane, które się skalują

Podczas tworzenia aplikacji często lepiej jest pobrać podzbiór listy. Jest to szczególnie powszechne, jeśli 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 konieczna może być denormalizacja tych danych. W wielu przypadkach można zdenormalizować dane, używając zapytania do pobrania podzbioru danych, jak omówiono w sekcji Sortowanie i filtrowanie danych .

Ale nawet to może być niewystarczające. Rozważmy na przykład dwukierunkową relację między użytkownikami i grupami. Użytkownicy mogą należeć do grupy, a grupy składają się z listy użytkowników. Kiedy przychodzi czas na podjęcie decyzji, do której grupy należy użytkownik, sprawy się komplikują.

Potrzebny jest elegancki sposób na wypisanie grup, do których należy użytkownik, i pobranie danych tylko dla tych grup. Indeks grup może tutaj bardzo pomóc:

// 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 to duplikuje niektóre dane, przechowując relację zarówno w rekordzie Ady, jak i w grupie. Teraz alovelace jest indeksowana w grupie, a techpioneers jest wymieniona w profilu Ady. Aby usunąć Adę z grupy, trzeba ją zaktualizować w dwóch miejscach.

Jest to niezbędna redundancja w przypadku relacji dwukierunkowych. Pozwala szybko i sprawnie pobrać członkostwo Ady, nawet gdy lista użytkowników lub grup skaluje się do milionów lub gdy reguły bezpieczeństwa Bazy Danych Czasu Rzeczywistego uniemożliwiają dostęp do niektórych rekordów.

Takie podejście, odwracanie 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ż wyszukiwanie lub skanowanie danych.

Następne kroki