Firebase Data Connect te permite crear conectores para tus instancias de PostgreSQL administradas con Google Cloud SQL. Estos conectores son combinaciones de consultas y mutaciones para usar tus datos desde tu esquema.
En la guía de inicio, se presentó un esquema de app de revisión de películas para PostgreSQL.
En esa guía, también se presentaron las operaciones administrativas implementables y ad hoc, incluidas las mutaciones.
- Las mutaciones implementables son aquellas que implementas para llamar desde apps cliente en un conector, con los extremos de API que defines. Data Connect integra la autenticación y la autorización en estas mutaciones, y genera SDKs de cliente basados en tu API.
- Las mutaciones administrativas ad hoc se ejecutan desde entornos privilegiados para completar y administrar tablas. Puedes crearlos y ejecutarlos en la consola de Firebase, desde entornos con privilegios con Firebase Admin SDK y en entornos de desarrollo locales con nuestra extensión de Data Connect para VS Code.
En esta guía, se analiza con mayor detalle el concepto de mutaciones implementables.
Características de las mutaciones de Data Connect
Data Connect te permite realizar mutaciones básicas de todas las formas que esperarías dada una base de datos de PostgreSQL:
- Realizar operaciones CRUD
- Administra operaciones de varios pasos con transacciones
Sin embargo, con las extensiones de Data Connect para GraphQL, puedes implementar mutaciones avanzadas para crear apps más rápidas y eficientes:
- Usa escalares de clave que muestran muchas operaciones para simplificar las operaciones repetidas en los registros.
- Usa valores del servidor para completar los datos con las operaciones que proporciona el servidor.
- Realiza consultas en el transcurso de operaciones de mutación de varios pasos para buscar datos, lo que permite ahorrar líneas de código y viajes de ida y vuelta al servidor.
Cómo usar campos generados para implementar mutaciones
Tus operaciones de Data Connect extenderán un conjunto de campos Data Connect generados automáticamente según los tipos y las relaciones de tipos en tu esquema. Estos campos se generan con herramientas locales cada vez que editas tu esquema.
Puedes usar campos generados para implementar mutaciones, desde la creación, actualización y eliminación de registros individuales en tablas únicas hasta actualizaciones más complejas de varias tablas.Supongamos que tu esquema contiene un tipo Movie
y un tipo Actor
asociado.
Data Connect genera los campos movie_insert
, movie_update
, movie_delete
y muchos más.
Mutación con el campo
movie_insert
El campo |
Usa este campo para crear una sola película. mutation CreateMovie($data: Movie_Data!) { movie_insert(data: $data) { key } } |
Mutación con el campo
movie_update
El campo |
Usa este campo para actualizar una sola película por su clave. mutation UpdateMovie($myKey: Movie_Key!, $data: Movie_Data!) { movie_update(key: $myKey, data: $data) { key } } |
Mutación con el campo
movie_delete
El campo |
Usa este campo para borrar una sola película por su clave. mutation DeleteMovie($myKey: Movie_Key!) { movie_delete(key: $myKey) { key } } |
Elementos esenciales de una mutación
Las mutaciones de Data Connect son mutaciones de GraphQL con extensiones de Data Connect. Al igual que con una mutación de GraphQL normal, puedes definir un nombre de operación y una lista de variables de GraphQL.
Data Connect extiende las consultas de GraphQL con directivas personalizadas, como @auth
y @transaction
.
Por lo tanto, la siguiente mutación tiene lo siguiente:
- Una definición de tipo
mutation
- Nombre de una operación
SignUp
(mutación) - Un solo argumento de operación
$username
- Una sola directiva,
@auth
- Un solo campo
user_insert
.
mutation SignUp($username: String!) @auth(level: USER) {
user_insert(data: {
id_expr: "auth.uid"
username: $username
})
}
Cada argumento de mutación requiere una declaración de tipo, ya sea un tipo integrado como String
o un tipo personalizado definido por el esquema como Movie
.
Escribe mutaciones básicas
Puedes comenzar a escribir mutaciones para crear, actualizar y borrar registros individuales de tu base de datos.
Crear
Hagamos creaciones básicas.
# 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
})
}
O una operación de 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"
})
}
Realiza actualizaciones
Aquí tienes las actualizaciones. Los productores y directores ciertamente esperan que esas calificaciones promedio sigan la tendencia.
El campo movie_update
contiene un argumento id
esperado para identificar un registro y un campo data
que puedes usar para establecer valores en esta actualización.
mutation UpdateMovie(
$id: UUID!,
$genre: String!,
$rating: Int!,
$description: String!
) {
movie_update(id: $id,
data: {
genre: $genre
rating: $rating
description: $description
})
}
Para realizar varias actualizaciones, usa el campo 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
})
}
Usa operaciones de incremento, disminución, anexión y anteposición con _update
Si bien en las mutaciones _update
y _updateMany
puedes establecer valores de forma explícita en data:
, a menudo tiene más sentido aplicar un operador como el incremento para actualizar los valores.
Para modificar el ejemplo de actualización anterior, supongamos que deseas aumentar la calificación de una película en particular. Puedes usar la sintaxis de rating_update
con el operador inc
.
mutation UpdateMovie(
$id: UUID!,
$ratingIncrement: Int!
) {
movie_update(id: $id, data: {
rating_update: {
inc: $ratingIncrement
}
})
}
Data Connect admite los siguientes operadores para las actualizaciones de campos:
inc
para incrementar los tipos de datosInt
,Int64
,Float
,Date
yTimestamp
dec
para disminuir los tipos de datosInt
,Int64
,Float
,Date
yTimestamp
En el caso de las listas, también puedes actualizar con valores individuales o listas de valores con lo siguiente:
add
para agregar elementos si aún no están presentes en los tipos de listas, excepto en las listas de vectoresremove
para quitar todos los elementos, si están presentes, de los tipos de listas, excepto las listas de vectoresappend
para agregar elementos a tipos de listas, excepto a las listas de vectoresprepend
para agregar elementos al principio de los tipos de listas, excepto las listas de vectores
Realizar eliminaciones
Por supuesto, puedes borrar los datos de las películas. Los conservacionistas de películas sin duda querrán que se mantengan las películas físicas durante el mayor tiempo posible.
# Delete by key
mutation DeleteMovie($id: UUID!) {
movie_delete(id: $id)
}
Aquí puedes usar _deleteMany
.
# Multiple deletes
mutation DeleteUnpopularMovies($minRating: Int!) {
movie_deleteMany(where: { rating: { le: $minRating } })
}
Escribe mutaciones en relaciones
Observa cómo usar la mutación _upsert
implícita en una relación.
# Create or update a one to one relation
mutation MovieMetadataUpsert($movieId: UUID!, $director: String!) {
movieMetadata_upsert(
data: { movie: { id: $movieId }, director: $director }
)
}
Diseña esquemas para mutaciones eficientes
Data Connect proporciona dos funciones importantes que te permiten escribir mutaciones más eficientes y ahorrar operaciones de ida y vuelta.
Los escalares clave son identificadores de objetos concisos que Data Connect ensambla automáticamente a partir de los campos clave de tus esquemas. Los escalares clave se relacionan con la eficiencia, ya que te permiten encontrar en una sola llamada información sobre la identidad y la estructura de tus datos. Son especialmente útiles cuando deseas realizar acciones secuenciales en registros nuevos y necesitas un identificador único para pasar a las próximas operaciones, y también cuando deseas acceder a claves relacionales para realizar operaciones adicionales más complejas.
Con los valores del servidor, puedes permitir que el servidor complete de forma dinámica los campos de tus tablas con valores almacenados o que se pueden calcular fácilmente según expresiones CEL particulares del servidor en el argumento expr
. Por ejemplo, puedes definir un campo con una marca de tiempo aplicada cuando se accede al campo con la hora almacenada en una solicitud de operación, updatedAt: Timestamp!
@default(expr: "request.time")
.
Escribe mutaciones avanzadas: Permite que Data Connect proporcione valores con la sintaxis de field_expr
Como se explicó en Valores clave escalares y del servidor, puedes diseñar tu esquema de modo que el servidor complete los valores de los campos comunes, como id
s y fechas, en respuesta a las solicitudes del cliente.
Además, puedes usar datos, como los IDs de usuario, que se envían en objetos Data Connect request
desde las apps para clientes.
Cuando implementes mutaciones, usa la sintaxis de field_expr
para activar actualizaciones generadas por el servidor o acceder a datos de solicitudes. Por ejemplo, para pasar la autorización uid
almacenada en una solicitud a una operación _upsert
, pasa "auth.uid"
en el campo 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 })
}
O bien, en una app de lista de tareas familiar, cuando crees una lista de tareas nueva, podrías pasar id_expr
para indicarle al servidor que genere automáticamente un UUID para la lista.
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,
})
}
Para obtener más información, consulta los escalares de _Expr
en la referencia de escalares.
Escribe mutaciones avanzadas: operaciones de varios pasos
Hay muchas situaciones en las que es posible que desees incluir varios campos de escritura (como inserciones) en una mutación. También es posible que desees leer tu base de datos durante la ejecución de una mutación para buscar y verificar los datos existentes antes de realizar, por ejemplo, inserciones o actualizaciones. Estas opciones ahorran operaciones de ida y vuelta y, por lo tanto, costos.
Data Connect te permite realizar lógica de varios pasos en tus mutaciones, ya que admite lo siguiente:
Varios campos de escritura
Varios campos de lectura en tus mutaciones (con la palabra clave del campo
query
)La directiva
@transaction
, que proporciona compatibilidad con transacciones, como en las bases de datos relacionales.La directiva
@check
, que te permite evaluar el contenido de las lecturas con expresiones de CEL y, según los resultados de esa evaluación, hacer lo siguiente:- Continuar con las operaciones de creación, actualización y eliminación definidas por una mutación
- Cómo devolver los resultados de un campo de consulta
- Usa los mensajes devueltos para realizar la lógica adecuada en tu código de cliente
La directiva
@redact
, que te permite omitir los resultados del campo de búsqueda de los resultados del protocolo de transferencia.La vinculación
response
de CEL, que almacena los resultados acumulados de todas las mutaciones y consultas realizadas en una operación compleja de varios pasos. Puedes acceder a la vinculaciónresponse
:- En las directivas
@check
, a través del argumentoexpr:
- Con valores del servidor, usando la sintaxis de
field_expr
- En las directivas
La directiva @transaction
La compatibilidad con mutaciones de varios pasos incluye el control de errores con transacciones.
La directiva @transaction
exige que una mutación, ya sea con un solo campo de escritura (por ejemplo, _insert
o _update
) o con varios campos de escritura, siempre se ejecute en una transacción de base de datos.
Las mutaciones sin
@transaction
ejecutan cada campo raíz uno después del otro en secuencia. La operación muestra los errores como errores de campos parciales, pero no los impactos de las ejecuciones posteriores.Se garantiza que las mutaciones con
@transaction
se completarán correctamente o fallarán por completo. Si falla alguno de los campos dentro de la transacción, se revierte toda la transacción.
Las directivas @check
y @redact
La directiva @check
verifica que los campos especificados estén presentes en los resultados de la búsqueda. Se usa una expresión de Common Expression Language (CEL) para probar los valores de los campos. El comportamiento predeterminado de la directiva es verificar y rechazar los nodos cuyo valor sea null
o []
(listas vacías).
La directiva @redact
oculta una parte de la respuesta del cliente. Los campos censurados aún se evalúan para detectar efectos secundarios (incluidos los cambios en los datos y @check
), y los resultados siguen disponibles para los pasos posteriores en las expresiones de CEL.
Usa @check
, @check(message:)
y @redact
Un uso importante de @check
y @redact
es buscar datos relacionados para decidir si se deben autorizar ciertas operaciones, usando la búsqueda en la lógica, pero ocultándola de los clientes. Tu búsqueda puede devolver mensajes útiles para el manejo correcto en el código del cliente.
A modo de ilustración, el siguiente campo de consulta verifica si un solicitante tiene un rol de "administrador" adecuado para ver a los usuarios que pueden editar una película.
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
}
}
}
Para obtener más información sobre las directivas @check
y @redact
en las verificaciones de autorización, consulta el análisis de la búsqueda de datos de autorización.
Usa @check
para validar claves
Algunos campos de mutación, como _update
, pueden no realizar ninguna operación si no existe un registro con una clave especificada. Del mismo modo, las búsquedas pueden devolver un valor nulo o una lista vacía. Estos no se consideran errores y, por lo tanto, no activarán reversiones.
Para evitar este resultado, prueba si se pueden encontrar claves con la directiva @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")
}
Usa la vinculación response
para encadenar mutaciones de varios pasos
El enfoque básico para crear registros relacionados, por ejemplo, un nuevo Movie
y una entrada MovieMetadata
asociada, es el siguiente:
- Llama a una mutación
_insert
paraMovie
- Almacena la clave devuelta de la película creada
- Luego, llama a una segunda mutación
_insert
para crear el registroMovieMetadata
.
Sin embargo, con Data Connect, puedes controlar este caso común en una sola operación de varios pasos accediendo a los resultados del primer _insert
en el segundo _insert
.
Crear una app exitosa de opiniones de películas requiere mucho trabajo. Hagamos un seguimiento de nuestra lista de tareas pendientes con un nuevo ejemplo.
Usa response
para establecer campos con valores del servidor
En la siguiente mutación de la lista de tareas pendientes:
- La vinculación
response
representa el objeto de respuesta parcial hasta el momento, que incluye todos los campos de mutación de nivel superior antes del actual. - Se accede a los resultados de la operación
todoList_insert
inicial, que devuelve el campoid
(clave), más adelante enresponse.todoList_insert.id
para que podamos insertar de inmediato un nuevo elemento de la lista de tareas pendientes.
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,
})
}
Usa response
para validar campos con @check
response
también está disponible en @check(expr: "...")
, por lo que puedes usarlo para compilar una lógica del servidor aún más complicada. En combinación con los pasos de query { … }
en las mutaciones, puedes lograr mucho más sin ningún viaje de ida y vuelta adicional entre el cliente y el servidor.
En el siguiente ejemplo, ten en cuenta que @check
ya tiene acceso a response.query
porque un @check
siempre se ejecuta después del paso al que está adjunto.
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,
})
}
Para obtener más información sobre la vinculación de response
, consulta la referencia de CEL.
Comprende las operaciones interrumpidas con @transaction
y query @check
Las mutaciones de varios pasos pueden generar errores:
- Es posible que fallen las operaciones de la base de datos.
- La lógica de la consulta
@check
puede finalizar las operaciones.
Data Connect recomienda que uses la directiva @transaction
con tus mutaciones de varios pasos. Esto genera una base de datos más coherente y resultados de mutación más fáciles de controlar en el código del cliente:
- La operación finalizará en el primer error o
@check
fallido, por lo que no es necesario administrar la ejecución de ningún campo posterior ni la evaluación de CEL. - Las reversiones se realizan en respuesta a errores de la base de datos o a la lógica de
@check
, lo que genera un estado coherente de la base de datos. - Siempre se devuelve un error de reversión al código del cliente.
Puede haber algunos casos de uso en los que elijas no usar @transaction
: Por ejemplo, podrías optar por la coherencia eventual si necesitas una mayor capacidad de procesamiento, escalabilidad o disponibilidad. Sin embargo, debes administrar tu base de datos y tu código de cliente para permitir los resultados:
- Si un campo falla debido a operaciones de la base de datos, los campos posteriores seguirán ejecutándose. Sin embargo, los
@check
s fallidos aún finalizan toda la operación. - No se realizan reversiones, lo que significa que la base de datos tiene un estado mixto con algunas actualizaciones exitosas y otras fallidas.
- Tus operaciones con
@check
pueden arrojar resultados más incoherentes si tu lógica de@check
usa los resultados de lecturas o escrituras en un paso anterior. - El resultado que se devuelve al código del cliente contendrá una combinación más compleja de respuestas de éxito y error que se deben controlar.
Directivas para mutaciones de Data Connect
Además de las directivas que usas para definir tipos y tablas, Data Connect proporciona las directivas @auth
, @check
, @redact
y @transaction
para aumentar el comportamiento de las operaciones.
Directiva | Se aplica a | Descripción |
---|---|---|
@auth |
Consultas y mutaciones | Define la política de autorización para una consulta o mutación. Consulta la guía de autorización y certificación. |
@check |
Campos query en operaciones de varios pasos |
Verifica que los campos especificados estén presentes en los resultados de la búsqueda. Se usa una expresión de Common Expression Language (CEL) para probar los valores de los campos. Consulta Operaciones de varios pasos. |
@redact |
Consultas | Oculta una parte de la respuesta del cliente. Consulta Operaciones de varios pasos. |
@transaction |
Mutaciones | Asegura que una mutación siempre se ejecute en una transacción de base de datos. Consulta Operaciones de varios pasos. |
Próximos pasos
También te puede interesar:
- Genera mutaciones para tus apps con herramientas de asistencia de IA
- Autoriza tus mutaciones según la guía de autorización.
- Cómo llamar a mutaciones desde el código de cliente para la Web, iOS, Android y Flutter
- Realizar operaciones de datos masivas con mutaciones