Data Connect-Mutationen implementieren

Mit Firebase Data Connect können Sie Connectors für Ihre PostgreSQL-Instanzen erstellen, die mit Google Cloud SQL verwaltet werden. Diese Connectors sind Kombinationen aus Abfragen und Mutationen, mit denen Sie Ihre Daten aus Ihrem Schema verwenden können.

In der Anleitung für die ersten Schritte wurde ein Schema für eine Filmbewertungs-App für PostgreSQL vorgestellt.

In dieser Anleitung wurden auch bereitstellbare und Ad-hoc-Verwaltungsvorgänge, einschließlich Mutationen, eingeführt.

  • Bereitstellbare Mutationen sind diejenigen, die Sie implementieren, um sie über Client-Apps in einem Connector mit von Ihnen definierten API-Endpunkten aufzurufen. Data Connect integriert die Authentifizierung und Autorisierung in diese Mutationen und generiert Client-SDKs basierend auf Ihrer API.
  • Ad-hoc-Mutations für die Verwaltung werden in privilegierten Umgebungen ausgeführt, um Tabellen zu füllen und zu verwalten. Sie können sie in der Firebase-Konsole, in privilegierten Umgebungen mit dem Firebase Admin SDK und in lokalen Entwicklungsumgebungen mit unserer Data Connect VS Code-Erweiterung erstellen und ausführen.

In diesem Leitfaden werden bereitstellbare Mutationen genauer betrachtet.

Funktionen von Data Connect-Mutationen

Mit Data Connect können Sie grundlegende Mutationen auf alle Arten ausführen, die Sie von einer PostgreSQL-Datenbank erwarten:

  • CRUD-Vorgänge ausführen
  • Mehrstufige Vorgänge mit Transaktionen verwalten

Mit den Erweiterungen von Data Connect für GraphQL können Sie jedoch erweiterte Mutationen für schnellere und effizientere Apps implementieren:

  • Verwenden Sie Schlüssel-Skalare, die von vielen Vorgängen zurückgegeben werden, um wiederholte Vorgänge für Datensätze zu vereinfachen.
  • Serverwerte verwenden, um Daten mit vom Server bereitgestellten Vorgängen zu füllen
  • Führen Sie Abfragen im Rahmen von mehrstufigen Mutationsvorgängen aus, um Daten abzurufen. So sparen Sie Codezeilen und Roundtrips zum Server.

Generierte Felder zum Implementieren von Mutationen verwenden

Ihre Data Connect-Vorgänge erweitern automatisch eine Reihe von Feldern, die auf Grundlage der Typen und Typbeziehungen in Ihrem Schema automatisch generiert werden.Data Connect Diese Felder werden von lokalen Tools generiert, wenn Sie Ihr Schema bearbeiten.

Sie können generierte Felder verwenden, um Mutationen zu implementieren, von der Erstellung, Aktualisierung und Löschung einzelner Datensätze in einzelnen Tabellen bis hin zu komplexeren Aktualisierungen mehrerer Tabellen.

Angenommen, Ihr Schema enthält einen Typ Movie und einen zugehörigen Typ Actor. Data Connect generiert die Felder movie_insert, movie_update, movie_delete und weitere.

Mutation mit dem Feld
movie_insert

Das Feld movie_insert stellt eine Mutation zum Erstellen eines einzelnen Datensatzes in der Tabelle Movie dar.

Verwenden Sie dieses Feld, um einen einzelnen Film zu erstellen.

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

Mutation mit dem Feld
movie_update

Das Feld movie_update stellt eine Mutation zum Aktualisieren eines einzelnen Datensatzes in der Tabelle Movie dar.

Mit diesem Feld können Sie einen einzelnen Film anhand seines Schlüssels aktualisieren.

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

Mutation mit dem Feld
movie_delete

Das Feld movie_delete stellt eine Mutation zum Löschen eines einzelnen Datensatzes in der Tabelle Movie dar.

Mit diesem Feld können Sie einen einzelnen Film anhand seines Schlüssels löschen.

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

Wesentliche Elemente einer Mutation

Data Connect-Mutationen sind GraphQL-Mutationen mit Data Connect-Erweiterungen. Genau wie bei einer regulären GraphQL-Mutation können Sie einen Vorgangsnamen und eine Liste von GraphQL-Variablen definieren.

Data Connect erweitert GraphQL-Abfragen mit benutzerdefinierten Direktiven wie @auth und @transaction.

Die folgende Mutation hat also:

  • Eine mutation-Typdefinition
  • Name eines SignUp-Vorgangs (Mutation)
  • Ein einzelnes Argument für den $username-Vorgang
  • Eine einzelne Richtlinie, @auth
  • Ein einzelnes Feld user_insert.
mutation SignUp($username: String!) @auth(level: USER) {
  user_insert(data: {
    id_expr: "auth.uid"
    username: $username
  })
}

Für jedes Mutationsargument ist eine Typdeklaration erforderlich, entweder ein integrierter Typ wie String oder ein benutzerdefinierter, schemadefinierter Typ wie Movie.

Grundlegende Mutationen schreiben

Sie können mit dem Schreiben von Mutationen beginnen, um einzelne Datensätze in Ihrer Datenbank zu erstellen, zu aktualisieren und zu löschen.

Erstellen

Wir beginnen mit einfachen Kreationen.

# 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
  })
}

Oder ein 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"
  })
}

Updates durchführen

Hier sind die Updates. Produzenten und Regisseure hoffen natürlich, dass diese durchschnittlichen Bewertungen im Trend liegen.

Das Feld movie_update enthält ein erwartetes id-Argument zur Identifizierung eines Datensatzes und ein data-Feld, mit dem Sie Werte in diesem Update festlegen können.

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

Verwenden Sie das Feld movie_updateMany, um mehrere Aktualisierungen vorzunehmen.

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

Inkrement-, Dekrement-, Append- und Prepend-Vorgänge mit _update verwenden

In _update- und _updateMany-Mutationen können Sie zwar explizit Werte in data: festlegen, es ist jedoch oft sinnvoller, einen Operator wie „increment“ (erhöhen) zu verwenden, um Werte zu aktualisieren.

Angenommen, Sie möchten das Beispiel für die Aktualisierung so ändern, dass die Bewertung eines bestimmten Films erhöht wird. Sie können die rating_update-Syntax mit dem inc-Operator verwenden.

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

Data Connect unterstützt die folgenden Operatoren für Feldaktualisierungen:

  • inc zum Erhöhen der Datentypen Int, Int64, Float, Date und Timestamp
  • dec zum Verringern der Datentypen Int, Int64, Float, Date und Timestamp

Bei Listen können Sie auch einzelne Werte oder Listen von Werten aktualisieren:

  • add, um Elemente anzuhängen, wenn sie noch nicht in Listentypen vorhanden sind, mit Ausnahme von Vektorlisten
  • remove – damit werden alle Elemente, sofern vorhanden, aus Listentypen mit Ausnahme von Vektorlisten entfernt.
  • append zum Anhängen von Elementen an Listentypen, außer Vektorlisten
  • prepend zum Voranstellen von Elementen an Listentypen, außer Vektorlisten

Löschvorgänge ausführen

Sie können Filmdaten natürlich löschen. Filmkonservatoren möchten sicherlich, dass die physischen Filme so lange wie möglich erhalten bleiben.

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

Hier können Sie _deleteMany verwenden.

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

Mutationen für Beziehungen schreiben

Sehen Sie sich an, wie die implizite Mutation _upsert für eine Beziehung verwendet wird.

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

Schemas für effiziente Mutationen entwerfen

Data Connect bietet zwei wichtige Funktionen, mit denen Sie effizientere Mutationen schreiben und Roundtrip-Vorgänge sparen können.

Schlüssel-Skalare sind prägnante Objekt-IDs, die Data Connect automatisch aus Schlüsselfeldern in Ihren Schemas zusammenstellt. Bei wichtigen Skalaren geht es um Effizienz. Sie können mit einem einzigen Aufruf Informationen zur Identität und Struktur Ihrer Daten abrufen. Sie sind besonders nützlich, wenn Sie sequenzielle Aktionen für neue Datensätze ausführen möchten und eine eindeutige Kennung für nachfolgende Vorgänge benötigen. Außerdem können Sie damit auf relationale Schlüssel zugreifen, um zusätzliche komplexere Vorgänge auszuführen.

Mit Serverwerten können Sie Felder in Ihren Tabellen dynamisch mit gespeicherten oder leicht berechenbaren Werten gemäß bestimmten serverseitigen CEL-Ausdrücken im expr-Argument füllen lassen. Sie können beispielsweise ein Feld mit einem Zeitstempel definieren, der angewendet wird, wenn auf das Feld mit der in einer Vorgangsanfrage gespeicherten Zeit updatedAt: Timestamp! @default(expr: "request.time") zugegriffen wird.

Erweiterte Mutationen schreiben: Data Connect kann Werte mit der field_expr-Syntax bereitstellen

Wie unter Wichtige Skalare und Serverwerte beschrieben, können Sie Ihr Schema so gestalten, dass der Server Werte für gängige Felder wie id und Datumsangaben als Reaktion auf Clientanfragen einfügt.

Außerdem können Sie Daten wie Nutzer-IDs verwenden, die in Data Connect-request-Objekten von Client-Apps gesendet werden.

Wenn Sie Mutationen implementieren, verwenden Sie die field_expr-Syntax, um serverseitig generierte Aktualisierungen auszulösen oder auf Daten aus Anfragen zuzugreifen. Wenn Sie beispielsweise die in einer Anfrage gespeicherte Autorisierung uid an einen _upsert-Vorgang übergeben möchten, übergeben Sie "auth.uid" im Feld 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 })
}

Oder Sie übergeben id_expr, wenn Sie in einer bekannten To-do-Listen-App eine neue To-do-Liste erstellen, um den Server anzuweisen, automatisch eine UUID für die Liste zu generieren.

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,
  })
}

Weitere Informationen finden Sie unter _Expr-Skalare in der Skalarreferenz.

Erweiterte Mutationen schreiben: Vorgänge mit mehreren Schritten

Es gibt viele Situationen, in denen Sie mehrere Schreibfelder (z. B. Einfügungen) in eine Mutation aufnehmen möchten. Möglicherweise möchten Sie auch während der Ausführung einer Mutation auf Ihre Datenbank zugreifen, um vorhandene Daten zu suchen und zu überprüfen, bevor Sie beispielsweise Einfügungen oder Aktualisierungen vornehmen. Mit diesen Optionen werden Roundtrip-Vorgänge und damit Kosten eingespart.

Mit Data Connect können Sie mehrstufige Logik in Ihren Mutationen ausführen, da Folgendes unterstützt wird:

  • Mehrere Schreibfelder

  • Mehrere Lesefelder in Ihren Mutationen (mit dem Feld-Keyword query).

  • Die @transaction-Anweisung bietet die aus relationalen Datenbanken bekannte Transaktionsunterstützung.

  • Die @check-Anweisung, mit der Sie den Inhalt von Lesevorgängen mithilfe von CEL-Ausdrücken auswerten können, und basierend auf den Ergebnissen dieser Auswertung:

    • Erstellen, Aktualisieren und Löschen gemäß einer Mutation
    • Ergebnisse eines Abfragefelds zurückgeben
    • Zurückgegebene Nachrichten verwenden, um die entsprechende Logik im Clientcode auszuführen
  • Die @redact-Anweisung, mit der Sie Abfragefeldergebnisse aus den Ergebnissen des Wire-Protokolls weglassen können.

  • Die CEL-Bindung response, in der die kumulierten Ergebnisse aller Mutationen und Abfragen gespeichert werden, die in einem komplexen, mehrstufigen Vorgang ausgeführt werden. Sie können auf die response-Bindung zugreifen:

    • In @check-Anweisungen über das Argument expr:
    • Mit Serverwerten, mit field_expr-Syntax

Die @transaction-Anweisung

Die Unterstützung von Mutationen mit mehreren Schritten umfasst die Fehlerbehandlung mithilfe von Transaktionen.

Die @transaction-Anweisung erzwingt, dass eine Mutation – entweder mit einem einzelnen Schreibfeld (z. B. _insert oder _update) oder mit mehreren Schreibfeldern – immer in einer Datenbanktransaktion ausgeführt wird.

  • Bei Mutationen ohne @transaction wird jedes Stammfeld nacheinander ausgeführt. Bei dem Vorgang werden alle Fehler als Fehler in Teilfeldern angezeigt, aber nicht die Auswirkungen der nachfolgenden Ausführungen.

  • Mutationen mit @transaction sind entweder vollständig erfolgreich oder vollständig fehlgeschlagen. Wenn eines der Felder in der Transaktion fehlschlägt, wird die gesamte Transaktion rückgängig gemacht.

Die Anweisungen @check und @redact

Mit der Anweisung @check wird geprüft, ob die angegebenen Felder in den Abfrageergebnissen vorhanden sind. Mit einem CEL-Ausdruck (Common Expression Language) werden Feldwerte getestet. Standardmäßig werden Knoten, deren Wert null oder [] (leere Listen) ist, geprüft und abgelehnt.

Mit der Direktive @redact wird ein Teil der Antwort für den Client unkenntlich gemacht. Entfernte Felder werden weiterhin auf Nebeneffekte (einschließlich Datenänderungen und @check) ausgewertet und die Ergebnisse sind weiterhin für spätere Schritte in CEL-Ausdrücken verfügbar.

@check, @check(message:) und @redact verwenden

@check und @redact werden hauptsächlich verwendet, um zu entscheiden, ob bestimmte Vorgänge autorisiert werden sollen. Die Suche erfolgt in der Logik, wird aber vor Clients verborgen. Ihre Anfrage kann nützliche Meldungen für die korrekte Verarbeitung im Clientcode zurückgeben.

Das folgende Abfragefeld prüft, ob ein Anfragender eine geeignete Administratorrolle hat, um Nutzer aufzurufen, die einen Film bearbeiten können.

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
    }
  }
}

Weitere Informationen zu @check- und @redact-Direktiven bei Autorisierungsprüfungen finden Sie im Abschnitt zum Nachschlagen von Autorisierungsdaten.

Schlüssel mit @check validieren

Einige Mutationsfelder wie _update haben möglicherweise keine Auswirkungen, wenn kein Datensatz mit einem angegebenen Schlüssel vorhanden ist. Ebenso können bei Lookups „null“ oder eine leere Liste zurückgegeben werden. Diese werden nicht als Fehler betrachtet und lösen daher keine Rollbacks aus.

Um dieses Ergebnis zu vermeiden, testen Sie, ob Schlüssel mit der @check-Anweisung gefunden werden können.

# 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")
}

response-Bindung verwenden, um Mutationen in mehreren Schritten zu verketten

Der grundlegende Ansatz zum Erstellen verknüpfter Datensätze, z. B. eines neuen Movie- und eines zugehörigen MovieMetadata-Eintrags, ist:

  1. _insert-Mutation für Movie aufrufen
  2. Den zurückgegebenen Schlüssel des erstellten Films speichern
  3. Rufen Sie dann eine zweite _insert-Mutation auf, um den MovieMetadata-Eintrag zu erstellen.

Mit Data Connect können Sie diesen häufigen Fall jedoch in einem einzigen mehrstufigen Vorgang bearbeiten, indem Sie in der zweiten _insert auf die Ergebnisse der ersten _insert zugreifen.

Eine erfolgreiche App für Filmrezensionen zu entwickeln, ist mit viel Arbeit verbunden. Sehen wir uns ein neues Beispiel für die Nachverfolgung unserer Aufgabenliste an.

Mit response Felder mit Serverwerten festlegen

In der folgenden Mutation für die Aufgabenliste:

  • Die response-Bindung stellt das bisherige Teilantwortobjekt dar, das alle Mutationsfelder der obersten Ebene vor dem aktuellen Feld enthält.
  • Die Ergebnisse des ursprünglichen todoList_insert-Vorgangs, der das Feld id (Schlüssel) zurückgibt, werden später in response.todoList_insert.id aufgerufen, damit wir sofort ein neues To-do-Element einfügen können.
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,
  })
}

Felder mit response validieren@check

response ist auch in @check(expr: "...") verfügbar. Sie können damit also noch komplexere serverseitige Logik erstellen. In Kombination mit query { … }-Schritten in Mutationen können Sie viel mehr erreichen, ohne zusätzliche Client-Server-Roundtrips durchzuführen.

Im folgenden Beispiel hat @check bereits Zugriff auf response.query, da @check immer nach dem Schritt ausgeführt wird, an den es angehängt ist.

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,
  })
}

Weitere Informationen zur response-Bindung finden Sie in der CEL-Referenz.

Unterbrochene Vorgänge mit @transaction und query @check nachvollziehen

Bei Mutationen mit mehreren Schritten können Fehler auftreten:

  • Datenbankvorgänge können fehlschlagen.
  • Die Logik der Abfrage @check kann Vorgänge beenden.

Data Connect empfiehlt, die @transaction-Anweisung für Mutationen mit mehreren Schritten zu verwenden. Dies führt zu einer konsistenteren Datenbank und Mutationsergebnissen, die im Clientcode einfacher zu verarbeiten sind:

  • Beim ersten Fehler oder fehlgeschlagenen @check wird der Vorgang beendet. Sie müssen also die Ausführung nachfolgender Felder oder die Auswertung von CEL nicht verwalten.
  • Rollbacks werden als Reaktion auf Datenbankfehler oder @check-Logik ausgeführt, um einen konsistenten Datenbankstatus zu erzielen.
  • Ein Rollback-Fehler wird immer an den Clientcode zurückgegeben.

Es gibt möglicherweise Anwendungsfälle, in denen Sie @transaction nicht verwenden. Sie können sich beispielsweise für Eventual Consistency entscheiden, wenn Sie einen höheren Durchsatz, eine höhere Skalierbarkeit oder Verfügbarkeit benötigen. Sie müssen jedoch Ihre Datenbank und Ihren Clientcode so verwalten, dass die Ergebnisse möglich sind:

  • Wenn ein Feld aufgrund von Datenbankvorgängen fehlschlägt, werden die nachfolgenden Felder weiterhin ausgeführt. Fehlgeschlagene @checks beenden jedoch den gesamten Vorgang.
  • Es werden keine Rollbacks durchgeführt. Das bedeutet, dass die Datenbank einen gemischten Status mit einigen erfolgreichen und einigen fehlgeschlagenen Updates aufweist.
  • Ihre Vorgänge mit @check können zu inkonsistenteren Ergebnissen führen, wenn in Ihrer @check-Logik die Ergebnisse von Lese- und/oder Schreibvorgängen in einem vorherigen Schritt verwendet werden.
  • Das an den Clientcode zurückgegebene Ergebnis enthält eine komplexere Mischung aus Erfolgs- und Fehlerantworten, die verarbeitet werden müssen.

Anweisungen für Data Connect-Mutationen

Zusätzlich zu den Anweisungen, die Sie zum Definieren von Typen und Tabellen verwenden, bietet Data Connect die Anweisungen @auth, @check, @redact und @transaction, um das Verhalten von Vorgängen zu erweitern.

Anweisung Gilt für Beschreibung
@auth Abfragen und Mutationen Definiert die Autorisierungsrichtlinie für eine Abfrage oder Mutation. Weitere Informationen finden Sie im Leitfaden zur Autorisierung und Attestierung.
@check query-Felder in mehrstufigen Vorgängen Prüft, ob die angegebenen Felder in den Abfrageergebnissen vorhanden sind. Mit einem CEL-Ausdruck (Common Expression Language) werden Feldwerte getestet. Weitere Informationen finden Sie unter Vorgänge mit mehreren Schritten.
@redact Abfragen Entfernt einen Teil der Antwort des Kunden. Weitere Informationen finden Sie unter Vorgänge mit mehreren Schritten.
@transaction Mutationen Erzwingt, dass eine Mutation immer in einer Datenbanktransaktion ausgeführt wird. Weitere Informationen finden Sie unter Vorgänge mit mehreren Schritten.

Nächste Schritte

Folgendes könnte Sie interessieren: