cloud firebase

Cloud Firestore w Firebase

Podziel się ze znajomymi

W tym wpisie skupimy się na usłudze Cloud FireStore, która jest NoSQLową bazą danych w chmurze. Aby zobaczyć różnice w kodzie pomiędzy Cloud FireStore, a Realtime Database, napiszemy taką samą aplikację jak poprzednio – Realtime Database w Firebase, jednak tym razem będziemy korzystać z FireStore. Wykorzystamy w tej aplikacji wszystkie mechanizmy omówione w poprzednim wpisie, m.in. modale.

Cloud FireStore jest elastyczną i skalowalną bazą danych, która przechowywuje dane w dokumentach. One zaś mogą być gromadzone w kolekcjach. Dokumenty obsługują różne typy danych, jednak dla JS nie mogą to być typy utworzone przez dewelopera. W dokumencie można również tworzyć podkolekcje i na ich podstawie budować hierarchiczną strukutrę danych, o czym dokładniej opowiem w kolejnym rozdziale tego wpisu.

Mamy możliwość pobierania danych z bazy w czasie rzeczywistym, ułatwia to życie dewelopera, ponieważ w momencie jakiejkolwiek zmiany w bazie, pojawiają się one od razu w aplikacji. Poza tym możemy wykorzystać tworzenie zapytań, które pozwalają przyśpieszyć przesyłanie danych, wykorzystywane są np. do tworzenia stronicowania.

Usługa ta pozwala na działanie aplikacji poza siecią. Dzięki buforowaniu danych można odczytywać, zapisywać, a także budować złożone zapytania, kiedy urządzenie jest poza siecią. W momencie ponownego podłączenia się do sieci, dane są synchronizowane z bazą danych.

Struktury danych

Podczas projektowania bazy danych mamy do dyspozycji różne opcje:

  • dokumenty,
  • stosowanie wielu kolekcji,
  • podkolekcje w dokumentach.

Twórcy Firebase w dokumentacji przedstawiają trzy podejścia do budowania bazy

  • Nested data in documents – zagnieżdżone dane w dokumencie
  • Subcollections – podkolekcje
  • Root-level collections – kolekcje na poziomie węzła głównego

Zagnieżdżone dane w dokumencie

Pierwsze z powyższych podejść polega na tworzeniu dokumentu z wieloma danymi. Wykorzystywane kiedy mamy proste, stałe listy danych. Wadą tego podejścia jest słaba skalowalność, w przypadku kiedy baza rozszerza się z czasem. Wraz ze zwiększającymi się listami rośnie również dokument, przez co wydłuża się czas pobierania danych. Możemy wykorzystać to podejście do utworzenia bazy filmów z oceną, rokiem produkcji, a także obsadą, poniżej znajduję się omówiony przykład.

{
  movieTitle: {
    rating,
    year,
    cast: [
      0: actor,
      1: actor,
      ...
    ]
  }
}

Podkolekcje

Podejście to polega na tworzeniu kolekcji w dokumentach, dzięki czemu powstaje hierarchiczna struktura danych. Najczęściej podejście to wykorzystywane jest, kiedy ilość danych może się rozszerzać.  Zwiększanie danych nie powoduje zmiany rozmiaru dokumentu. Poza tym mamy możliwość tworzenia zapytań bezpośrednio do podkolekcji. Wadą tego rozwiązania jest problematyczne usuwanie podkolekcji. Wykorzystać to możemy tworząc kolekcje filmów, w której znajduję się kolekcja komentarzy. Poniżej znajduje się przykład wykorzystania tego podejścia.

{
  movies: [
    idMovie: {
      title
      rating,
      comments: [
        idComments: {
          user,
          body
        },
        ...
      ]
    }
  ]
}

Kolekcje na poziomie węzła głownego

Ostatni ze sposobów projektowania bazy danych polega na tworzeniu kolekcji na poziomie głównego węzła bazy danych. Sposób ten zapewnia największa elastyczność oraz skalowalność. Wadą jest wzrost złożoności danych wraz ze wzrostem bazy danych. Przykładem użycia tego podejścia może być aplikacja do wystawiania recenzji filmowych. W takiej strukturze mamy kolekcję użytkowników oraz filmów. Poniżej znajduje się omówiony przykład.

{
  users: {
    idUser: {
      username,
      firstName,
      lastName
    }
  },
  movies: {
    idMovie: {
      title
      rating,
      comments: [
        idComments: {
          user,
          body
        },
        ...
      ]
    }
  }
}

Prezentacja aplikacji

Poniżej znajdują się screeny prezentujące działanie aplikacji.

Strona główna po zalogowaniu.

cloud-firestore-dashboard

Formularz do edytowania danych.

cloud-firestore-update

Okno potwierdzenia usunięcia wybranego filmu.

cloud-firestore-delete

Implementacja

Przypomnę, że we wpisie Realtime Database w Firebase mamy zamieszczony sposób implementacji rzeczy związanych z interfejsem, dlatego w tym wpisie omówię jedynie implementację Cloud FireStore.

Zaczynamy od dodania biblioteki obsługujące omawianą bazę danych w pliku index.html.

<script src="https://www.gstatic.com/firebasejs/6.3.3/firebase-firestore.js"></script>

Cloud FireStore

Teraz możemy już przystąpić do implementacji obsługi bazy. Zaczniemy od wczytywania danych w czasie rzeczywistym. Poniżej znajduje się kod za to odpowiedzialny.

static getData() {
  firebase.firestore().collection('users').doc(firebase.auth().currentUser.uid).collection('movies').onSnapshot(querySnapshot => {
    UI.resetList();
    querySnapshot.forEach((doc) => {
      const movie = new Movie(doc.data().title, doc.data().rating, doc.id);
      UI.addMovie(movie);
    });
  });
}

Na początku za pomocą metod collection() oraz doc() określamy ścieżkę, pod jaką znajdują się dane. W bazie zaprojektowanej przeze mnie, dane znajdują się pod ścieżką

users/idUsers/movies

Następnie wywołana jest metoda onSnapshot(), która odpowiada za pobieranie danych w czasie rzeczywistym. Jako parametr przyjmuje funkcję, która zwraca dane z bazy. Pierwszym krokiem po otrzymaniu danych jest wyczyszczenie listy wyświetlanych filmów. Następnie przechodzimy za pomocą pętli przez wszystkie otrzymane dokumenty. Dla każdego dokumentu tworzymy obiekt klasy Movie i dodajemy film do listy wyświetlanej w Dashboardzie.

Kolejną funkcjonalnością jaką się zajmiemy jest dodawanie dokumentów do bazy. Poniżej znajduje się kod.

static addFavoriteMovie() {
  const movieTitle = document.getElementById('movieTitle').value;
  const movieRating = document.getElementById('movieRating').value;

  if (!movieTitle || !movieRating) {
    return;
  }

  const movie = new Movie(movieTitle, movieRating);

  firebase.firestore().collection('users').doc(firebase.auth().currentUser.uid).collection('movies').add({
      title: movie.title,
      rating: movie.rating
    })
    .then(response => {
      document.getElementById('movieTitle').value = '';
      document.getElementById('movieRating').value = '0';
    })
    .catch(error => {
      console.log(error.message);
    });
}

Po pobraniu danych z formularza, sprawdzeniu czy któreś z pól nie było puste oraz utworzeniu obiektu klasy Movie możemy przystąpić do obsługi dodawania danych do bazy. Podobnie jak przy pobieraniu danych z bazy, tu także określamy ścieżkę, a następnie wywołujemy metodę add(), która odpowiada za dodanie dokumentu do bazy i wygenerowanie dla niego identyfikatora. W parametrze przekazywany jest obiekt, jaki ma zostać zachowany w dokumencie. W przypadku powodzenia operacji formularz jest czyszczony, zaś w przypadku niepowodzenia – zostanie wyświetlony w konsoli komunikat z błędem.

W przypadku aktualizacji danych postępujemy podobnie, tylko zamiast używać metody add() wykorzystujemy metodę update(), która aktualizuje tylko wybrane pola, bez konieczności nadpisywania całego dokumentu. Poniżej znajduje się kod aktualizujący dane.

static updateFavoriteMovie() {
  const title = document.getElementById('movieTitleEdit').value;
  const rating = document.getElementById('movieRatingEdit').value;

  if (!title || !rating) {
    return;
  }

  firebase.firestore().collection('users').doc(firebase.auth().currentUser.uid).collection('movies').doc(Modal.getID())
    .update({
      title,
      rating
    })
    .then(response => {})
    .catch(error => {
      console.log(error.message);
    });
}

Pozostało nam zaimplementować usuwanie dokumentów. Poniżej mamy kod realizujący tą funkcjonalność.

static deleteFavoriteMovie() {
  if (!Modal.getID()) {
    return;
  }

  firebase.firestore().collection('users').doc(firebase.auth().currentUser.uid).collection('movies').doc(Modal.getID()).delete()
    .then(response => {})
    .catch(error => {
      console.log(error.message);
    });
}

Po określeniu ścieżki do dokumentu, którego chcemy się pozbyć, wywołujemy metodę delete(). W przypadku wystąpienia błędu wyświetlamy komunikat o błędzie zwrócony przez Firebase.

Teraz musimy wywołać utworzone metody i wszystko powinno działać, tak samo jak miało to miejsce w przypadku Realtime Database.

document.addEventListener('DOMContentLoaded', () => {
    firebase.auth().onAuthStateChanged(user => {
        if (user) {
            UI.updateUsername();
            UI.showDashboardView();
            Firebase.getData();
        } else {
            UI.showLoginView();
        }
    });
});

Po załadowaniu strony wywołujemy metodę Firebase.getData(), dzięki czemu nie będziemy mieć problemu z próbą dodania filmów do listy, która jeszcze nie istnieje.

document.getElementById('movieAddBtn').addEventListener('click', e => {
  Firebase.addFavoriteMovie();
});

document.getElementById('editConfirmBtn').addEventListener('click', e => {
  Firebase.updateFavoriteMovie();
  UI.hideModal();
});

document.getElementById('deleteConfirmBtn').addEventListener('click', e => {
  Firebase.deleteFavoriteMovie();
  UI.hideModal();
});

Powyżej znajdują się kod odpowiadający za nasłuchiwanie zdarzeń, odpowiedzialne za dodawanie, edytowanie i usuwanie danych z bazy.

Podsumowanie

Cloud FireStore to zupełnie inne podejście do przechowywania danych w porównaniu do Realtime Database. Podział na dokumenty oraz kolekcje sprawia, że dane przechowywane są w bardziej zrozumiałej strukturze. Pobieranie danych w czasie rzeczywistym sprowadza się do utworzenia jednej metody, co w przypadku poprzednio omawianej bazy danych było realizowane za pomocą trzech. Jak można zobaczyć powyżej, obsługa Cloud FireStore jest bardzo prosta i wiele rzeczy upraszcza w porównaniu do Realtime Database.

Dokumentacja Cloud Firestore

Zalecane struktury bazy danych

Kod do utworzonej we wpisie aplikacji – katalog L3_firestore

One thought on “Cloud Firestore w Firebase

Dodaj komentarz

Twój adres email nie zostanie opublikowany. Pola, których wypełnienie jest wymagane, są oznaczone symbolem *