Firebase Data Connect vous permet de créer des connecteurs pour vos instances PostgreSQL gérées avec Google Cloud SQL. Ces connecteurs sont des combinaisons de requêtes et de mutations permettant d'utiliser les données de votre schéma.
Le guide de démarrage a présenté un schéma d'application d'avis sur des films pour PostgreSQL.
Ce guide présentait également les opérations administratives déployables et ponctuelles, y compris les mutations.
- Les mutations déployables sont celles que vous implémentez pour appeler à partir d'applications clientes dans un connecteur, avec les points de terminaison d'API que vous définissez. Data Connect intègre l'authentification et l'autorisation dans ces mutations, et génère des SDK clients en fonction de votre API.
- Les mutations administratives ponctuelles sont exécutées à partir d'environnements privilégiés pour remplir et gérer les tables. Vous pouvez les créer et les exécuter dans la console Firebase, à partir d'environnements privilégiés à l'aide de Firebase Admin SDK, et dans des environnements de développement locaux à l'aide de notre extension VS Code Data Connect.
Ce guide examine plus en détail les mutations déployables.
Fonctionnalités des mutations Data Connect
Data Connect vous permet d'effectuer des mutations de base de toutes les manières possibles avec une base de données PostgreSQL :
- Effectuer des opérations CRUD
- Gérer les opérations en plusieurs étapes avec des transactions
Toutefois, grâce aux extensions de Data Connect pour GraphQL, vous pouvez implémenter des mutations avancées pour des applications plus rapides et plus efficaces :
- Utilisez les scalaires de clé renvoyés par de nombreuses opérations pour simplifier les opérations répétées sur les enregistrements.
- Utilisez les valeurs de serveur pour remplir les données avec les opérations fournies par le serveur.
- Exécutez des requêtes au cours d'opérations de mutation en plusieurs étapes pour rechercher des données, ce qui vous permet d'économiser des lignes de code et des allers-retours vers le serveur.
Utiliser des champs générés pour implémenter des mutations
Vos opérations Data Connect étendront un ensemble de champs Data Connect générés automatiquement en fonction des types et des relations de type dans votre schéma. Ces champs sont générés par des outils locaux chaque fois que vous modifiez votre schéma.
Vous pouvez utiliser des champs générés pour implémenter des mutations, de la création, la mise à jour et la suppression d'enregistrements individuels dans des tables uniques aux mises à jour multitables plus complexes.Supposons que votre schéma contienne un type Movie
et un type Actor
associé.
Data Connect génère les champs movie_insert
, movie_update
, movie_delete
et plus encore.
Mutation avec le champ
movie_insert
Le champ |
Utilisez ce champ pour créer un film. mutation CreateMovie($data: Movie_Data!) { movie_insert(data: $data) { key } } |
Mutation avec le champ
movie_update
Le champ |
Utilisez ce champ pour mettre à jour un seul film à l'aide de sa clé. mutation UpdateMovie($myKey: Movie_Key!, $data: Movie_Data!) { movie_update(key: $myKey, data: $data) { key } } |
Mutation avec le champ
movie_delete
Le champ |
Utilisez ce champ pour supprimer un film à l'aide de sa clé. mutation DeleteMovie($myKey: Movie_Key!) { movie_delete(key: $myKey) { key } } |
Éléments essentiels d'une mutation
Les mutations Data Connect sont des mutations GraphQL avec des extensions Data Connect. Comme pour une mutation GraphQL classique, vous pouvez définir un nom d'opération et une liste de variables GraphQL.
Data Connect étend les requêtes GraphQL avec des directives personnalisées telles que @auth
et @transaction
.
La mutation suivante comporte :
- Définition du type
mutation
- Nom d'une opération
SignUp
(mutation) - Argument d'opération
$username
à une seule variable - Une seule directive,
@auth
- Un seul champ
user_insert
.
mutation SignUp($username: String!) @auth(level: USER) {
user_insert(data: {
id_expr: "auth.uid"
username: $username
})
}
Chaque argument de mutation nécessite une déclaration de type, un type intégré tel que String
ou un type personnalisé défini par le schéma tel que Movie
.
Écrire des mutations de base
Vous pouvez commencer à écrire des mutations pour créer, mettre à jour et supprimer des enregistrements individuels de votre base de données.
Créer
Effectuons des créations de base.
# 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
})
}
Ou une opération d'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"
})
}
Effectuer des mises à jour
Voici les dernières informations. Les producteurs et les réalisateurs espèrent certainement que ces notes moyennes sont en phase avec les tendances.
Le champ movie_update
contient un argument id
attendu pour identifier un enregistrement et un champ data
que vous pouvez utiliser pour définir des valeurs dans cette mise à jour.
mutation UpdateMovie(
$id: UUID!,
$genre: String!,
$rating: Int!,
$description: String!
) {
movie_update(id: $id,
data: {
genre: $genre
rating: $rating
description: $description
})
}
Pour effectuer plusieurs mises à jour, utilisez le champ 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
})
}
Utiliser les opérations d'incrémentation, de décrémentation, d'ajout et de préfixe avec _update
Bien que vous puissiez définir explicitement des valeurs dans data:
lors des mutations _update
et _updateMany
, il est souvent plus judicieux d'appliquer un opérateur tel que "increment" pour mettre à jour les valeurs.
Pour modifier l'exemple de mise à jour précédent, supposons que vous souhaitiez incrémenter la note d'un film en particulier. Vous pouvez utiliser la syntaxe rating_update
avec l'opérateur inc
.
mutation UpdateMovie(
$id: UUID!,
$ratingIncrement: Int!
) {
movie_update(id: $id, data: {
rating_update: {
inc: $ratingIncrement
}
})
}
Data Connect accepte les opérateurs suivants pour les mises à jour de champs :
inc
pour incrémenter les types de donnéesInt
,Int64
,Float
,Date
etTimestamp
dec
pour décrémenter les types de donnéesInt
,Int64
,Float
,Date
etTimestamp
Pour les listes, vous pouvez également effectuer des mises à jour avec des valeurs individuelles ou des listes de valeurs à l'aide des éléments suivants :
add
pour ajouter des éléments s'ils ne sont pas déjà présents dans les types de listes, à l'exception des listes de vecteursremove
pour supprimer tous les éléments des types de listes, le cas échéant, à l'exception des listes de vecteursappend
pour ajouter des éléments aux types de listes, à l'exception des listes de vecteursprepend
pour ajouter un ou plusieurs éléments au début des types de listes, à l'exception des listes de vecteurs
Effectuer des suppressions
Vous pouvez bien sûr supprimer les données de films. Les spécialistes de la conservation des films souhaiteront certainement que les films physiques soient conservés le plus longtemps possible.
# Delete by key
mutation DeleteMovie($id: UUID!) {
movie_delete(id: $id)
}
Vous pouvez utiliser _deleteMany
.
# Multiple deletes
mutation DeleteUnpopularMovies($minRating: Int!) {
movie_deleteMany(where: { rating: { le: $minRating } })
}
Écrire des mutations sur les relations
Découvrez comment utiliser la mutation _upsert
implicite sur une relation.
# Create or update a one to one relation
mutation MovieMetadataUpsert($movieId: UUID!, $director: String!) {
movieMetadata_upsert(
data: { movie: { id: $movieId }, director: $director }
)
}
Concevoir des schémas pour des mutations efficaces
Data Connect fournit deux fonctionnalités importantes qui vous permettent d'écrire des mutations plus efficaces et d'économiser des opérations aller-retour.
Les scalaires clés sont des identifiants d'objet concis que Data Connect assemble automatiquement à partir des champs clés de vos schémas. Les scalaires clés concernent l'efficacité. Ils vous permettent de trouver en un seul appel des informations sur l'identité et la structure de vos données. Ils sont particulièrement utiles lorsque vous souhaitez effectuer des actions séquentielles sur de nouveaux enregistrements et que vous avez besoin d'un identifiant unique à transmettre aux opérations à venir. Ils le sont également lorsque vous souhaitez accéder à des clés relationnelles pour effectuer des opérations supplémentaires plus complexes.
En utilisant les valeurs de serveur, vous pouvez laisser le serveur remplir dynamiquement les champs de vos tableaux à l'aide de valeurs stockées ou facilement calculables, selon des expressions CEL côté serveur spécifiques dans l'argument expr
. Par exemple, vous pouvez définir un champ avec un code temporel appliqué lorsque le champ est consulté à l'aide de l'heure stockée dans une requête d'opération, updatedAt: Timestamp!
@default(expr: "request.time")
.
Écrire des mutations avancées : laisser Data Connect fournir des valeurs à l'aide de la syntaxe field_expr
Comme indiqué dans Scalaires clés et valeurs de serveur, vous pouvez concevoir votre schéma de sorte que le serveur renseigne les valeurs des champs courants tels que les id
et les dates en réponse aux requêtes des clients.
De plus, vous pouvez utiliser des données telles que les ID utilisateur envoyés dans les objets Data Connect request
des applications clientes.
Lorsque vous implémentez des mutations, utilisez la syntaxe field_expr
pour déclencher des mises à jour générées par le serveur ou accéder aux données des requêtes. Par exemple, pour transmettre l'autorisation uid
stockée dans une requête à une opération _upsert
, transmettez "auth.uid"
dans le champ 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 })
}
Dans une application de liste de tâches familière, lorsque vous créez une liste de tâches, vous pouvez transmettre id_expr
pour demander au serveur de générer automatiquement un UUID pour la liste.
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,
})
}
Pour en savoir plus, consultez les scalaires _Expr
dans la documentation de référence sur les scalaires.
Écrire des mutations avancées : opérations en plusieurs étapes
Il existe de nombreuses situations dans lesquelles vous pouvez inclure plusieurs champs d'écriture (comme des insertions) dans une même mutation. Vous pouvez également lire votre base de données lors de l'exécution d'une mutation pour rechercher et valider les données existantes avant d'effectuer, par exemple, des insertions ou des mises à jour. Ces options permettent d'économiser des opérations aller-retour et donc des coûts.
Data Connect vous permet d'effectuer une logique en plusieurs étapes dans vos mutations en prenant en charge les éléments suivants :
Champs d'écriture multiples
Plusieurs champs de lecture dans vos mutations (à l'aide du mot clé de champ
query
).La directive
@transaction
, qui fournit une assistance pour les transactions similaire à celle des bases de données relationnelles.L'instruction
@check
, qui vous permet d'évaluer le contenu des lectures à l'aide d'expressions CEL et, en fonction des résultats de cette évaluation :- Effectuer les créations, les mises à jour et les suppressions définies par une mutation
- Poursuivez pour renvoyer les résultats d'un champ de requête.
- Utilisez les messages renvoyés pour effectuer la logique appropriée dans votre code client.
La directive
@redact
, qui vous permet d'omettre les résultats des champs de requête dans les résultats du protocole filaire.L'association CEL
response
, qui stocke les résultats cumulés de toutes les mutations et requêtes effectuées dans une opération complexe en plusieurs étapes. Vous pouvez accéder à la liaisonresponse
:- Dans les directives
@check
, via l'argumentexpr:
- Avec des valeurs de serveur, en utilisant la syntaxe
field_expr
- Dans les directives
Directive @transaction
La prise en charge des mutations en plusieurs étapes inclut la gestion des erreurs à l'aide de transactions.
La directive @transaction
impose qu'une mutation (avec un seul champ d'écriture, par exemple _insert
ou _update
, ou avec plusieurs champs d'écriture) s'exécute toujours dans une transaction de base de données.
Les mutations sans
@transaction
exécutent chaque champ racine l'un après l'autre, dans l'ordre. L'opération affiche les erreurs sous forme d'erreurs de champ partielles, mais pas les impacts des exécutions ultérieures.Les mutations avec
@transaction
sont garanties de réussir ou d'échouer complètement. Si l'un des champs de la transaction échoue, l'intégralité de la transaction est annulée.
Directives @check
et @redact
La directive @check
vérifie que les champs spécifiés sont présents dans les résultats de la requête. Une expression CEL (Common Expression Language) est utilisée pour tester les valeurs des champs. Le comportement par défaut de la directive consiste à rechercher et à rejeter les nœuds dont la valeur est null
ou []
(listes vides).
La directive @redact
masque une partie de la réponse du client. Les champs masqués sont toujours évalués pour les effets secondaires (y compris les modifications de données et @check
), et les résultats restent disponibles pour les étapes ultérieures dans les expressions CEL.
Utiliser @check
, @check(message:)
et @redact
@check
et @redact
sont principalement utilisés pour rechercher des données associées afin de déterminer si certaines opérations doivent être autorisées, en utilisant la recherche dans la logique, mais en la masquant aux clients. Votre requête peut renvoyer des messages utiles pour une gestion correcte dans le code client.
Pour illustrer cela, le champ de requête suivant vérifie si un demandeur dispose d'un rôle d'administrateur approprié pour afficher les utilisateurs qui peuvent modifier un 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
}
}
}
Pour en savoir plus sur les directives @check
et @redact
dans les vérifications d'autorisation, consultez la discussion sur la recherche de données d'autorisation.
Utiliser @check
pour valider les clés
Certains champs de mutation, tels que _update
, peuvent ne rien faire si un enregistrement avec une clé spécifiée n'existe pas. De même, les recherches peuvent renvoyer une valeur nulle ou une liste vide. Elles ne sont pas considérées comme des erreurs et ne déclenchent donc pas de rétablissement.
Pour éviter ce résultat, testez si des clés peuvent être trouvées à l'aide de la directive @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")
}
Utiliser la liaison response
pour enchaîner des mutations en plusieurs étapes
L'approche de base pour créer des enregistrements associés, par exemple un Movie
et une entrée MovieMetadata
associée, consiste à :
- Appeler une mutation
_insert
pourMovie
- Stocker la clé renvoyée du film créé
- Appelez ensuite une deuxième mutation
_insert
pour créer l'enregistrementMovieMetadata
.
Toutefois, avec Data Connect, vous pouvez gérer ce cas courant en une seule opération en plusieurs étapes en accédant aux résultats du premier _insert
dans le deuxième _insert
.
Créer une application d'avis sur les films qui fonctionne bien demande beaucoup de travail. Suivons notre liste de tâches à l'aide d'un nouvel exemple.
Utiliser response
pour définir des champs avec des valeurs de serveur
Dans la mutation de la liste de tâches suivante :
- La liaison
response
représente l'objet de réponse partielle jusqu'à présent, qui inclut tous les champs de mutation de premier niveau avant celui en cours. - Les résultats de l'opération
todoList_insert
initiale, qui renvoie le champid
(clé), sont accessibles ultérieurement dansresponse.todoList_insert.id
afin que nous puissions insérer immédiatement un nouvel élément de liste de tâches.
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,
})
}
Utiliser response
pour valider les champs à l'aide de @check
response
est également disponible dans @check(expr: "...")
. Vous pouvez donc l'utiliser pour créer une logique côté serveur encore plus complexe. Combiné aux étapes query { … }
dans les mutations, vous pouvez accomplir beaucoup plus sans aucun aller-retour client-serveur supplémentaire.
Dans l'exemple suivant, notez que @check
a déjà accès à response.query
, car un @check
s'exécute toujours après l'étape à laquelle il est associé.
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,
})
}
Pour en savoir plus sur la liaison response
, consultez la documentation de référence sur CEL.
Comprendre les opérations interrompues avec @transaction
et query @check
Des erreurs peuvent se produire lors des mutations en plusieurs étapes :
- Les opérations sur la base de données peuvent échouer.
- La logique de la requête
@check
peut mettre fin aux opérations.
Data Connect vous recommande d'utiliser la directive @transaction
avec vos mutations en plusieurs étapes. Cela permet d'obtenir une base de données plus cohérente et des résultats de mutation plus faciles à gérer dans le code client :
- L'opération s'arrête à la première erreur ou au premier
@check
ayant échoué. Il n'est donc pas nécessaire de gérer l'exécution des champs suivants ni l'évaluation de CEL. - Les rollbacks sont effectués en réponse à des erreurs de base de données ou à une logique
@check
, ce qui permet d'obtenir un état cohérent de la base de données. - Une erreur de rétablissement est toujours renvoyée au code client.
Dans certains cas d'utilisation, vous pouvez choisir de ne pas utiliser @transaction
. Par exemple, vous pouvez opter pour la cohérence à terme si vous avez besoin d'un débit, d'une évolutivité ou d'une disponibilité plus élevés. Toutefois, vous devez gérer votre base de données et votre code client pour autoriser les résultats :
- Si un champ échoue en raison d'opérations de base de données, les champs suivants continueront de s'exécuter. Toutefois, les
@check
ayant échoué mettent toujours fin à l'opération dans son ensemble. - Les rollbacks ne sont pas effectués, ce qui signifie que l'état de la base de données est mixte (certaines mises à jour ont réussi et d'autres ont échoué).
- Vos opérations avec
@check
peuvent donner des résultats plus incohérents si votre logique@check
utilise les résultats de lectures et/ou d'écritures d'une étape précédente. - Le résultat renvoyé au code client contiendra un mélange plus complexe de réponses de réussite et d'échec à traiter.
Directives pour les mutations Data Connect
En plus des directives que vous utilisez pour définir des types et des tables, Data Connect fournit les directives @auth
, @check
, @redact
et @transaction
pour augmenter le comportement des opérations.
Directive | Applicable à | Description |
---|---|---|
@auth |
Requêtes et mutations | Définit la règle d'autorisation pour une requête ou une mutation. Consultez le guide sur l'autorisation et l'attestation. |
@check |
Champs query dans les opérations en plusieurs étapes |
Vérifie que les champs spécifiés sont présents dans les résultats de la requête. Une expression CEL (Common Expression Language) est utilisée pour tester les valeurs des champs. Consultez Opérations en plusieurs étapes. |
@redact |
Requêtes | Masque une partie de la réponse du client. Consultez Opérations en plusieurs étapes. |
@transaction |
Mutations | Garantit qu'une mutation s'exécute toujours dans une transaction de base de données. Consultez Opérations en plusieurs étapes. |
Étapes suivantes
Vous pourriez être intéressé par :
- Générer des mutations pour vos applications à l'aide des outils d'assistance par IA
- Autoriser vos mutations conformément au guide d'autorisation
- Appeler des mutations à partir de votre code client pour Web, iOS, Android et Flutter.
- Effectuer des opérations sur les données groupées avec des mutations