Implementowanie mutacji Data Connectors

Firebase Data Connect umożliwia tworzenie łączników do instancji PostgreSQL zarządzanych za pomocą Google Cloud SQL. Te konektory to kombinacje zapytań i mutacji, które umożliwiają korzystanie z danych ze schematu.

W przewodniku dla początkujących przedstawiliśmy schemat aplikacji do oceniania filmów w PostgreSQL.

W tym przewodniku przedstawiliśmy też operacje administracyjne, które można wdrożyć i wykonać ad hoc, w tym mutacje.

  • Mutacje, które można wdrożyć, to te, które implementujesz, aby wywoływać je z aplikacji klienckich w złączu, z zdefiniowanymi przez Ciebie punktami końcowymi interfejsu API. Data Connect integruje uwierzytelnianie i autoryzację z tymi mutacjami oraz generuje pakiety SDK klientaData Connect na podstawie Twojego interfejsu API.
  • Mutacje administracyjne ad hoc są uruchamiane w środowiskach z uprawnieniami do wypełniania tabel i zarządzania nimi. Możesz je tworzyć i wykonywać w konsoli Firebase, w środowiskach z uprawnieniami za pomocą Firebase Admin SDK oraz w lokalnych środowiskach programistycznych za pomocą naszego rozszerzenia Data Connect do VS Code.

W tym przewodniku znajdziesz szczegółowe informacje o mutacjach, które można wdrożyć.

Cechy mutacji Data Connect

Data Connect umożliwia wykonywanie podstawowych mutacji na wszystkie sposoby, jakich można oczekiwać w przypadku bazy danych PostgreSQL:

  • Wykonywanie operacji CRUD
  • Zarządzanie wieloetapowymi operacjami za pomocą transakcji

Dzięki rozszerzeniom Data Connect do GraphQL możesz jednak wdrażać zaawansowane mutacje, aby tworzyć szybsze i wydajniejsze aplikacje:

  • Używaj skalarów kluczy zwracanych przez wiele operacji, aby uprościć powtarzające się operacje na rekordach.
  • Używaj wartości serwera, aby wypełniać dane operacjami dostarczanymi przez serwer.
  • Wykonywanie zapytań w trakcie wieloetapowych operacji mutacji w celu wyszukiwania danych, co pozwala zaoszczędzić wiersze kodu i podróże do serwera.

Używanie wygenerowanych pól do wdrażania mutacji

Operacje Data Connect rozszerzą zestaw pól automatycznie wygenerowanych Data Connect na podstawie typów i relacji między typami w schemacie. Pola te są generowane przez narzędzia lokalne za każdym razem, gdy edytujesz schemat.

Wygenerowanych pól możesz używać do wdrażania mutacji, od tworzenia, aktualizowania i usuwania poszczególnych rekordów w pojedynczych tabelach po bardziej złożone aktualizacje w wielu tabelach.

Załóżmy, że schemat zawiera typ Movie i powiązany z nim typ Actor. Data Connect generuje pola movie_insert, movie_update, movie_delete i inne.

Mutacja z polem
movie_insert

Pole movie_insert reprezentuje mutację, która tworzy pojedynczy rekord w tabeli Movie.

Użyj tego pola, aby utworzyć jeden film.

mutation CreateMovie($data: Movie_Data!) {
  movie_insert(data: $data) { key }
}

Mutacja z polem
movie_update

Pole movie_update reprezentuje mutację, która służy do aktualizowania pojedynczego rekordu w tabeli Movie.

Użyj tego pola, aby zaktualizować pojedynczy film według klucza.

mutation UpdateMovie($myKey: Movie_Key!, $data: Movie_Data!) {
  movie_update(key: $myKey, data: $data) { key }
}

Mutacja z polem
movie_delete

Pole movie_delete reprezentuje mutację, która powoduje usunięcie pojedynczego rekordu z tabeli Movie.

Użyj tego pola, aby usunąć pojedynczy film według klucza.

  mutation DeleteMovie($myKey: Movie_Key!) {
    movie_delete(key: $myKey) { key }
  }

Najważniejsze elementy mutacji

Mutacje Data Connect to mutacje GraphQL z Data Connectrozszerzeniami. Podobnie jak w przypadku zwykłej mutacji GraphQL możesz zdefiniować nazwę operacji i listę zmiennych GraphQL.

Data Connect rozszerza zapytania GraphQL o niestandardowe dyrektywy, takie jak @auth@transaction.

W związku z tym ta mutacja ma:

  • Definicja typu mutation
  • Nazwa SignUpoperacji (zmiany)
  • Argument operacji z jedną zmienną $username
  • jedna dyrektywa, @auth
  • Pojedyncze pole user_insert.
mutation SignUp($username: String!) @auth(level: USER) {
  user_insert(data: {
    id_expr: "auth.uid"
    username: $username
  })
}

Każdy argument mutacji wymaga deklaracji typu, wbudowanego, np. String, lub niestandardowego, zdefiniowanego w schemacie, np. Movie.

Zapisz podstawowe mutacje

Możesz zacząć pisać mutacje, aby tworzyć, aktualizować i usuwać poszczególne rekordy z bazy danych.

Utwórz

Zacznijmy od podstawowych kreacji.

# Create a movie based on user input
mutation CreateMovie($title: String!, $releaseYear: Int!, $genre: String!, $rating: Int!) {
  movie_insert(data: {
    title: $title
    releaseYear: $releaseYear
    genre: $genre
    rating: $rating
  })
}

# Create a movie with default values
mutation CreateMovie2 {
  movie_insert(data: {
    title: "Sherlock Holmes"
    releaseYear: 2009
    genre: "Mystery"
    rating: 5
  })
}

lub upsert.

# Movie upsert using combination of variables and literals
mutation UpsertMovie($title: String!) {
  movie_upsert(data: {
    title: $title
    releaseYear: 2009
    genre: "Mystery"
    rating: 5
    genre: "Mystery/Thriller"
  })
}

Przeprowadzanie aktualizacji

Oto aktualności. Producenci i reżyserzy z pewnością mają nadzieję, że te średnie oceny są zgodne z trendami.

Pole movie_update zawiera oczekiwany argument id do identyfikowania rekordu i pole data, którego możesz użyć do ustawiania wartości w tej aktualizacji.

mutation UpdateMovie(
  $id: UUID!,
  $genre: String!,
  $rating: Int!,
  $description: String!
) {
  movie_update(id: $id,
    data: {
      genre: $genre
      rating: $rating
      description: $description
    })
}

Aby wprowadzić kilka zmian, użyj pola movie_updateMany.

# Multiple updates (increase all ratings of a genre)
mutation IncreaseRatingForGenre($genre: String!, $rating: Int!) {
  movie_updateMany(
    where: { genre: { eq: $genre } },
    data:
      {
        rating: $rating
      })
}

Używanie operacji zwiększania, zmniejszania, dołączania i dodawania na początku z elementem _update

W przypadku mutacji _update_updateMany możesz jawnie ustawiać wartości w data:, ale często bardziej opłaca się zastosować operator taki jak increment, aby zaktualizować wartości.

Aby zmodyfikować wcześniejszy przykład aktualizacji, załóżmy, że chcesz zwiększyć ocenę konkretnego filmu. Możesz używać składni rating_update z operatorem inc.

mutation UpdateMovie(
  $id: UUID!,
  $ratingIncrement: Int!
) {
  movie_update(id: $id, data: {
    rating_update: {
      inc: $ratingIncrement
    }
  })
}

Data Connect obsługuje te operatory w przypadku aktualizacji pól:

  • inc, aby zwiększyć typy danych Int, Int64, Float, DateTimestamp
  • dec, aby zmniejszyć wartości typów danych Int, Int64, Float, DateTimestamp.

W przypadku list możesz też aktualizować poszczególne wartości lub listy wartości, korzystając z tych opcji:

  • add, aby dołączyć elementy, jeśli nie ma ich jeszcze na listach(z wyjątkiem list wektorowych);
  • remove – aby usunąć wszystkie elementy z list, z wyjątkiem list wektorowych.
  • append – dołączanie elementów do typów list, z wyjątkiem list wektorowych;
  • prepend – dodawanie elementów na początku list, z wyjątkiem list wektorów.

Usuwanie

Możesz oczywiście usunąć dane filmu. Osoby zajmujące się konserwacją filmów z pewnością będą chciały, aby fizyczne kopie filmów były przechowywane jak najdłużej.

# Delete by key
mutation DeleteMovie($id: UUID!) {
  movie_delete(id: $id)
}

Możesz tu użyć _deleteMany.

# Multiple deletes
mutation DeleteUnpopularMovies($minRating: Int!) {
  movie_deleteMany(where: { rating: { le: $minRating } })
}

Zapisywanie mutacji w relacjach

Zobacz, jak używać niejawnej mutacji _upsert w relacji.

# Create or update a one to one relation
mutation MovieMetadataUpsert($movieId: UUID!, $director: String!) {
  movieMetadata_upsert(
    data: { movie: { id: $movieId }, director: $director }
  )
}

Projektowanie schematów pod kątem wydajnych mutacji

Data Connect udostępnia 2 ważne funkcje, które pozwalają pisać bardziej wydajne mutacje i oszczędzać operacje typu round-trip.

Kluczowe skalary to zwięzłe identyfikatory obiektów, które Data Connect automatycznie tworzy na podstawie kluczowych pól w Twoich schematach. Kluczowe skalary dotyczą wydajności, ponieważ umożliwiają uzyskanie w ramach jednego wywołania informacji o tożsamości i strukturze danych. Są one szczególnie przydatne, gdy chcesz wykonywać sekwencyjne działania na nowych rekordach i potrzebujesz unikalnego identyfikatora do przekazywania do kolejnych operacji, a także gdy chcesz uzyskać dostęp do kluczy relacyjnych, aby wykonywać dodatkowe, bardziej złożone operacje.

Korzystając z wartości serwera, możesz skutecznie zezwolić serwerowi na dynamiczne wypełnianie pól w tabelach za pomocą przechowywanych lub łatwo obliczalnych wartości zgodnie z określonymi wyrażeniami CEL po stronie serwera w argumencie expr. Możesz na przykład zdefiniować pole z sygnaturą czasową, która jest stosowana, gdy uzyskuje się do niego dostęp przy użyciu czasu przechowywanego w żądaniu operacji, updatedAt: Timestamp! @default(expr: "request.time").

Pisanie zaawansowanych mutacji: pozwól Data Connect dostarczać wartości za pomocą składni field_expr

Jak wspomnieliśmy w sekcji kluczowe wartości skalarne i wartości serwera, możesz zaprojektować schemat tak, aby serwer wypełniał wartości w przypadku typowych pól, takich jak id i daty, w odpowiedzi na żądania klientów.

Możesz też korzystać z danych, takich jak identyfikatory użytkowników, przesyłanych w obiektach Data Connect request z aplikacji klienckich.

Podczas wdrażania mutacji używaj składni field_expr, aby wywoływać aktualizacje generowane przez serwer lub uzyskiwać dostęp do danych z żądań. Na przykład, aby przekazać autoryzację uid przechowywaną w żądaniu do operacji _upsert, przekaż "auth.uid" w polu userId_expr.

# Add a movie to the user's favorites list
mutation AddFavoritedMovie($movieId: UUID!) @auth(level: USER) {
  favorite_movie_upsert(data: { userId_expr: "auth.uid", movieId: $movieId })
}

# Remove a movie from the user's favorites list
mutation DeleteFavoritedMovie($movieId: UUID!) @auth(level: USER) {
  favorite_movie_delete(key: { userId_expr: "auth.uid", movieId: $movieId })
}

W znanej aplikacji do tworzenia list zadań podczas tworzenia nowej listy możesz przekazać znak id_expr, aby poinstruować serwer, aby automatycznie wygenerował identyfikator UUID dla listy.

mutation CreateTodoListWithFirstItem(
  $listName: String!
) @transaction {
  # Step 1
  todoList_insert(data: {
    id_expr: "uuidV4()", # <-- auto-generated. Or a column-level @default on `type TodoList` will also work
    name: $listName,
  })
}

Więcej informacji znajdziesz w _Exprskalarach w dokumentacji skalarów.

Pisanie zaawansowanych mutacji: operacje wieloetapowe

W jednej mutacji możesz uwzględnić wiele pól zapisu (np. wstawień). Możesz też chcieć odczytać bazę danych podczas wykonywania mutacji, aby wyszukać i zweryfikować istniejące dane przed wykonaniem np. wstawień lub aktualizacji. Te opcje pozwalają zaoszczędzić operacje w obie strony, a tym samym koszty.

Data Connect umożliwia wykonywanie wieloetapowej logiki w mutacjach przez obsługę:

  • Wiele pól do pisania

  • Wiele pól odczytu w mutacjach (z użyciem słowa kluczowego pola query).

  • @transactionDyrektywa, która zapewnia obsługę transakcji znaną z relacyjnych baz danych.

  • @check dyrektywa, która umożliwia ocenę zawartości odczytów za pomocą wyrażeń CEL i na podstawie wyników tej oceny:

    • Przeprowadzanie operacji tworzenia, aktualizowania i usuwania zdefiniowanych przez mutację
    • Przejdź do zwracania wyników pola zapytania
    • Używaj zwróconych wiadomości do wykonywania odpowiedniej logiki w kodzie klienta.
  • @redactDyrektywa, która umożliwia pomijanie wyników pól zapytań w wynikach protokołu przewodowego.

  • Powiązanie CEL response, które przechowuje zgromadzone wyniki wszystkich mutacji i zapytań wykonanych w ramach złożonej, wieloetapowej operacji. Możesz uzyskać dostęp do wiązania response:

    • @check dyrektywach za pomocą argumentu expr:
    • Wartości serwera, używając składni field_expr

Dyrektywa @transaction

Obsługa mutacji wieloetapowych obejmuje obsługę błędów za pomocą transakcji.

Dyrektywa @transaction wymusza, aby mutacja z jednym polem zapisu (np. _insert lub _update) lub z wieloma polami zapisu zawsze była wykonywana w transakcji bazy danych.

  • Mutacje bez @transaction wykonują każde pole główne po kolei. Operacja zgłasza błędy jako błędy częściowe, ale nie informuje o skutkach kolejnych wykonań.

  • Mutacje oznaczone symbolem @transaction gwarantują pełny sukces lub pełną porażkę. Jeśli którekolwiek z pól w transakcji nie zostanie przetworzone, cała transakcja zostanie wycofana.

Dyrektywy @check@redact

Dyrektywa @check sprawdza, czy w wynikach zapytania znajdują się określone pola. Do testowania wartości pól używane jest wyrażenie w języku Common Expression Language (CEL). Domyślne działanie dyrektywy polega na sprawdzaniu i odrzucaniu węzłów, których wartość to null lub [] (puste listy).

Dyrektywa @redact usuwa część odpowiedzi klienta. Zaciemnione pola są nadal oceniane pod kątem efektów ubocznych (w tym zmian danych i @check), a wyniki są nadal dostępne w późniejszych krokach wyrażeń CEL.

Użyj właściwości @check, @check(message:)@redact.

Głównym zastosowaniem funkcji @check@redact jest wyszukiwanie powiązanych danych w celu podjęcia decyzji, czy zezwolić na określone operacje. Wyszukiwanie jest używane w logice, ale jest ukryte przed klientami. Zapytanie może zwracać przydatne komunikaty, które umożliwiają prawidłową obsługę w kodzie klienta.

Na przykład to pole zapytania sprawdza, czy osoba wysyłająca żądanie ma odpowiednią rolę „administratora”, aby wyświetlić użytkowników, którzy mogą edytować film.

query GetMovieEditors($movieId: UUID!) @auth(level: USER) {
  moviePermission(key: { movieId: $movieId, userId_expr: "auth.uid" }) @redact {
    role @check(expr: "this == 'admin'", message: "You must be an admin to view all editors of a movie.")
  }
  moviePermissions(where: { movieId: { eq: $movieId }, role: { eq: "editor" } }) {
    user {
      id
      username
    }
  }
}

Więcej informacji o dyrektywach @check@redact w sprawdzaniu autoryzacji znajdziesz w omówieniu wyszukiwania danych autoryzacyjnych.

Używanie urządzenia @check do weryfikacji kluczy

Niektóre pola mutacji, np. _update, mogą nie działać, jeśli rekord z określonym kluczem nie istnieje. Podobnie wyszukiwania mogą zwracać wartość null lub pustą listę. Nie są one uznawane za błędy, więc nie powodują wycofania zmian.

Aby temu zapobiec, sprawdź, czy klucze można znaleźć za pomocą dyrektywy @check.

# Delete by key, error if not found
mutation MustDeleteMovie($id: UUID!) @transaction {
  movie_delete(id: $id) @check(expr: "this != null", message: "Movie not found, therefore nothing is deleted")
}

Używaj powiązania response do łączenia mutacji wieloetapowych

Podstawowe podejście do tworzenia powiązanych rekordów, np. nowego rekordu Movie i powiązanego wpisu MovieMetadata, polega na wykonaniu tych czynności:

  1. Wywołanie mutacji _insert dla Movie
  2. Przechowywanie zwróconego klucza utworzonego filmu
  3. Następnie wywołaj drugą mutację _insert, aby utworzyć rekord MovieMetadata.

Dzięki Data Connect możesz jednak obsłużyć ten typowy przypadek w ramach jednej wieloetapowej operacji, uzyskując dostęp do wyników pierwszego _insert w drugim _insert.

Stworzenie aplikacji do recenzowania filmów, która odniesie sukces, wymaga dużo pracy. Śledźmy listę zadań do wykonania na nowym przykładzie.

Używanie response do ustawiania pól z wartościami serwera

W tej mutacji listy zadań:

  • Wiązanie response reprezentuje dotychczasowy obiekt częściowej odpowiedzi, który zawiera wszystkie pola mutacji najwyższego poziomu przed bieżącym polem.
  • Wyniki początkowej operacji todoList_insert, która zwraca pole id (klucz), są dostępne później w operacji response.todoList_insert.id, dzięki czemu możemy od razu wstawić nowe zadanie do wykonania.
mutation CreateTodoListWithFirstItem(
  $listName: String!,
  $itemContent: String!
) @transaction {
  # Sub-step 1:
  todoList_insert(data: {
    id_expr: "uuidV4()", # <-- auto-generated. Or a column-level @default on `type TodoList` will also work
    name: $listName,
  })
  # Sub-step 2:
  todo_insert(data: {
    listId_expr: "response.todoList_insert.id" # <-- Grab the newly generated ID from the partial response so far.
    content: $itemContent,
  })
}

Użyj polecenia response, aby zweryfikować pola za pomocą funkcji @check

response jest też dostępny w @check(expr: "..."), więc możesz go używać do tworzenia jeszcze bardziej złożonych mechanizmów logicznych po stronie serwera. W połączeniu z query { … } krokami w mutacjach możesz osiągnąć znacznie więcej bez dodatkowych podróży w obie strony między klientem a serwerem.

W przykładzie poniżej zwróć uwagę, że @check ma już dostęp do response.query, ponieważ @check zawsze jest uruchamiany po kroku, do którego jest dołączony.

mutation CreateTodoInNamedList(
  $listName: String!,
  $itemContent: String!
) @transaction {
  # Sub-step 1: Look up List.id by its name
  query
  @check(expr: "response.query.todoLists.size() > 0", message: "No such TodoList with the name!")
  @check(expr: "response.query.todoLists.size() < 2", message: "Ambiguous listName!") {
    todoLists(where: { name: $listName }) {
      id
    }
  }
  # Sub-step 2:
  todo_insert(data: {
    listId_expr: "response.todoLists[0].id" # <-- Now we have the parent list ID to insert to
    content: $itemContent,
  })
}

Więcej informacji o powiązaniu response znajdziesz w dokumentacji referencyjnej CEL.

Zrozumienie przerwanych operacji za pomocą symboli @transactionquery @check

W przypadku mutacji wieloetapowych mogą wystąpić błędy:

  • Operacje na bazie danych mogą się nie powieść.
  • logika zapytania @check może zakończyć operacje.

Data Connect zaleca używanie dyrektywy @transaction w przypadku mutacji wieloetapowych. Dzięki temu baza danych jest bardziej spójna, a wyniki mutacji są łatwiejsze do obsługi w kodzie klienta:

  • Operacja zostanie zakończona przy pierwszym błędzie lub nieudanym @check, więc nie musisz zarządzać wykonywaniem kolejnych pól ani oceną CEL.
  • Cofanie zmian jest wykonywane w odpowiedzi na błędy bazy danych lub @checklogikę, co zapewnia spójny stan bazy danych.
  • Błąd wycofania jest zawsze zwracany do kodu klienta.

W niektórych przypadkach możesz nie używać @transaction. Możesz na przykład wybrać spójność ostateczną, jeśli potrzebujesz większej przepustowości, skalowalności lub dostępności. Musisz jednak zarządzać bazą danych i kodem klienta, aby umożliwić uzyskanie wyników:

  • Jeśli jedno pole nie zostanie przetworzone z powodu operacji na bazie danych, kolejne pola będą nadal wykonywane. Jednak nieudane @check nadal przerywają całą operację.
  • Wycofywanie zmian nie jest wykonywane, co oznacza, że baza danych może mieć stan mieszany, w którym niektóre aktualizacje zostały przeprowadzone, a niektóre nie.
  • Operacje z użyciem @check mogą dawać bardziej niespójne wyniki, jeśli logika @check korzysta z wyników odczytu lub zapisu z poprzedniego kroku.
  • Wynik zwrócony do kodu klienta będzie zawierał bardziej złożoną kombinację odpowiedzi o powodzeniu i niepowodzeniu, które należy obsłużyć.

Dyrektywy dotyczące mutacji Data Connect

Oprócz dyrektyw używanych do definiowania typów i tabel Data Connect udostępnia dyrektywy @auth, @check, @redact@transaction, które rozszerzają działanie operacji.

Dyrektywa Dotyczy Opis
@auth Zapytania i mutacje Definiuje zasadę autoryzacji dla zapytania lub mutacji. Zapoznaj się z przewodnikiem po autoryzacji i atestowaniu.
@check query pól w operacjach wieloetapowych Sprawdza, czy w wynikach zapytania znajdują się określone pola. Do testowania wartości pól używa się wyrażenia w języku Common Expression Language (CEL). Zobacz Operacje wieloetapowe.
@redact Zapytania Usuwa część odpowiedzi klienta. Zobacz Operacje wieloetapowe.
@transaction Mutacje Wymusza, aby mutacja zawsze była wykonywana w transakcji bazy danych. Zobacz Operacje wieloetapowe.

Dalsze kroki

Może Cię zainteresować: