Com o Firebase Data Connect, é possível criar conectores para suas instâncias do PostgreSQL gerenciadas com o Google Cloud SQL. Esses conectores são combinações de consultas e mutações para usar os dados do seu esquema.
O guia de início rápido apresentou um esquema de app de avaliação de filmes para PostgreSQL.
Esse guia também apresentou operações administrativas implantáveis e ad hoc, incluindo mutações.
- As mutações implantáveis são aquelas que você implementa para chamar de apps clientes em um conector, com endpoints de API definidos por você. O Data Connect integra autenticação e autorização a essas mutações e gera SDKs de cliente com base na sua API.
- As mutações administrativas ad hoc são executadas em ambientes privilegiados para preencher e gerenciar tabelas. É possível criar e executar essas consultas no console Firebase, em ambientes privilegiados usando o Firebase Admin SDK e em ambientes de desenvolvimento local usando a extensão do Data Connect para VS Code.
Este guia analisa mais detalhadamente as mutações implantáveis.
Recursos das mutações de Data Connect
O Data Connect permite realizar mutações básicas de todas as maneiras esperadas em um banco de dados PostgreSQL:
- Realizar operações CRUD
- Gerenciar operações de várias etapas com transações
Mas com as extensões do Data Connect para GraphQL, é possível implementar mutações avançadas para apps mais rápidos e eficientes:
- Use escalares de chave retornados por muitas operações para simplificar operações repetidas em registros.
- Use valores do servidor para preencher dados com operações fornecidas pelo servidor.
- Realize consultas no decorrer de operações de mutação de várias etapas para pesquisar dados, economizando linhas de código e viagens de ida e volta ao servidor.
Usar campos gerados para implementar mutações
Suas operações Data Connect vão estender um conjunto de campos Data Connect gerados automaticamente com base nos tipos e nas relações de tipo no seu esquema. Esses campos são gerados por ferramentas locais sempre que você edita o esquema.
É possível usar campos gerados para implementar mutações, desde a criação, atualização e exclusão de registros individuais em tabelas únicas até atualizações mais complexas em várias tabelas.Suponha que seu esquema contenha um tipo Movie e um tipo Actor associado.
O Data Connect gera os campos movie_insert, movie_update, movie_delete e muito mais.
Mutação com o campo
movie_insert
|
O campo |
Use este campo para criar um único filme. mutation CreateMovie($data: Movie_Data!) { movie_insert(data: $data) { key } } |
Mutação com o campo
movie_update
|
O campo |
Use este campo para atualizar um único filme pela chave. mutation UpdateMovie($myKey: Movie_Key!, $data: Movie_Data!) { movie_update(key: $myKey, data: $data) { key } } |
Mutação com o campo
movie_delete
|
O campo |
Use este campo para excluir um único filme pela chave. mutation DeleteMovie($myKey: Movie_Key!) { movie_delete(key: $myKey) { key } } |
Elementos essenciais de uma mutação
As mutações do Data Connect são mutações GraphQL com extensões Data Connect. Assim como em uma mutação normal do GraphQL, você pode definir um nome de operação e uma lista de variáveis do GraphQL.
O Data Connect estende as consultas GraphQL com diretivas personalizadas, como
@auth e @transaction.
Portanto, a seguinte mutação tem:
- Uma definição de tipo
mutation - Um nome de operação (mutação)
SignUp - Um único argumento de operação de variável
$username - Uma única diretiva,
@auth - Um único campo
user_insert.
mutation SignUp($username: String!) @auth(level: USER) {
user_insert(data: {
id_expr: "auth.uid"
username: $username
})
}
Todo argumento de mutação exige uma declaração de tipo, um tipo integrado como String ou um tipo personalizado definido pelo esquema, como Movie.
Escrever mutações básicas
Você pode começar a escrever mutações para criar, atualizar e excluir registros individuais do seu banco de dados.
Criar
Vamos fazer criações 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
})
}
Ou um 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"
})
}
Fazer atualizações
Confira as atualizações. Produtores e diretores certamente esperam que essas classificações médias estejam na tendência.
O campo movie_update contém um argumento id esperado para identificar um registro
e um campo data que pode ser usado para definir valores nessa atualização.
mutation UpdateMovie(
$id: UUID!,
$genre: String!,
$rating: Int!,
$description: String!
) {
movie_update(id: $id,
data: {
genre: $genre
rating: $rating
description: $description
})
}
Para fazer várias atualizações, use o 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
})
}
Use operações de incremento, decremento, anexação e pré-anexação com _update
Embora nas mutações _update e _updateMany seja possível definir valores explicitamente em
data:, geralmente é mais sensato aplicar um operador como incremento para atualizar
valores.
Para modificar o exemplo de atualização anterior, suponha que você queira aumentar a classificação de um filme específico. É possível usar a sintaxe rating_update com o operador inc.
mutation UpdateMovie(
$id: UUID!,
$ratingIncrement: Int!
) {
movie_update(id: $id, data: {
rating_update: {
inc: $ratingIncrement
}
})
}
O Data Connect aceita os seguintes operadores para atualizações de campo:
incpara incrementar os tipos de dadosInt,Int64,Float,DateeTimestampdecpara diminuir os tipos de dadosInt,Int64,Float,DateeTimestamp
Para listas, também é possível atualizar com valores individuais ou listas de valores usando:
addpara anexar itens se eles ainda não estiverem presentes nos tipos de lista, exceto listas de vetores.removepara remover todos os itens, se houver, dos tipos de lista, exceto listas de vetoresappendpara anexar itens a tipos de lista, exceto listas de vetoresprependpara adicionar itens aos tipos de lista, exceto listas de vetores
Realizar exclusões
É claro que você pode excluir os dados de filmes. Os preservacionistas de filmes certamente vão querer que os filmes físicos sejam mantidos pelo maior tempo possível.
# Delete by key
mutation DeleteMovie($id: UUID!) {
movie_delete(id: $id)
}
Aqui você pode usar o _deleteMany.
# Multiple deletes
mutation DeleteUnpopularMovies($minRating: Int!) {
movie_deleteMany(where: { rating: { le: $minRating } })
}
Gravar mutações em relações
Observe como usar a mutação _upsert implícita em uma relação.
# Create or update a one to one relation
mutation MovieMetadataUpsert($movieId: UUID!, $director: String!) {
movieMetadata_upsert(
data: { movie: { id: $movieId }, director: $director }
)
}
Projetar esquemas para mutações eficientes
O Data Connect oferece dois recursos importantes que permitem escrever mutações mais eficientes e economizar operações de ida e volta.
Escalares de chave são identificadores de objetos concisos que o Data Connect monta automaticamente com base em campos-chave nos seus esquemas. Os escalares principais são sobre eficiência, permitindo que você encontre em uma única chamada informações sobre a identidade e a estrutura dos seus dados. Elas são especialmente úteis quando você quer realizar ações sequenciais em novos registros e precisa de um identificador exclusivo para transmitir às próximas operações, e também quando quer acessar chaves relacionais para realizar outras operações mais complexas.
Usando valores do servidor, você pode permitir que o servidor preencha dinamicamente os campos nas tabelas usando valores armazenados ou facilmente calculáveis de acordo com expressões CEL específicas do lado do servidor no argumento expr. Por exemplo, é possível definir um campo com um carimbo de data/hora aplicado quando ele é acessado usando o tempo armazenado em uma solicitação de operação, updatedAt: Timestamp!
@default(expr: "request.time").
Escrever mutações avançadas: permitir que Data Connect forneça valores usando a sintaxe field_expr
Como discutido em escalares de chave e valores do servidor,
você pode projetar seu esquema para que o servidor preencha valores de campos
comuns, como ids e datas, em resposta a solicitações do cliente.
Além disso, é possível usar dados, como IDs de usuário, enviados em objetos Data Connect request de apps clientes.
Ao implementar mutações, use a sintaxe field_expr para acionar
atualizações geradas pelo servidor ou acessar dados de solicitações. Por exemplo, para transmitir a autorização uid armazenada em uma solicitação para uma operação _upsert, transmita "auth.uid" no 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 })
}
Ou, em um app de lista de tarefas conhecido, ao criar uma nova lista, você pode
transmitir id_expr para instruir o servidor a gerar automaticamente um UUID para a 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 mais informações, consulte os escalares _Expr na
referência de escalares.
Escrever mutações avançadas: operações de várias etapas
Há muitas situações em que você pode querer incluir vários campos de gravação (como inserções) em uma mutação. Você também pode querer ler seu banco de dados durante a execução de uma mutação para pesquisar e verificar os dados existentes antes de realizar, por exemplo, inserções ou atualizações. Essas opções economizam operações de ida e volta e, portanto, custos.
O Data Connect permite realizar uma lógica de várias etapas nas mutações com suporte para:
Vários campos de gravação
Vários campos de leitura nas suas mutações (usando a palavra-chave do campo
query).A diretiva
@transaction, que oferece suporte a transações conhecido dos bancos de dados relacionais.A diretiva
@check, que permite avaliar o conteúdo das leituras usando expressões CEL e, com base nos resultados dessa avaliação:- Continuar com criações, atualizações e exclusões definidas por uma mutação
- Continuar para retornar os resultados de um campo de consulta
- Use as mensagens retornadas para executar a lógica apropriada no código do cliente.
A diretiva
@redact, que permite omitir resultados de campos de consulta dos resultados do protocolo de rede.A vinculação
responseda CEL, que armazena os resultados acumulados de todas as mutações e consultas realizadas em uma operação complexa de várias etapas. Você pode acessar a vinculaçãoresponse:- Em diretivas
@check, usando o argumentoexpr: - Com valores do servidor, usando a sintaxe
field_expr
- Em diretivas
A diretiva @transaction
O suporte para mutações de várias etapas inclui o tratamento de erros usando transações.
A diretiva @transaction garante que uma mutação, com um único campo de gravação (por exemplo, _insert ou _update) ou com vários campos de gravação, sempre seja executada em uma transação de banco de dados.
Mutações sem
@transactionexecutam cada campo raiz um após o outro em sequência. A operação mostra erros como erros de campo parciais, mas não os impactos das execuções subsequentes.As mutações com
@transactiontêm garantia de sucesso ou falha total. Se algum dos campos na transação falhar, toda a transação será revertida.
As diretivas @check e @redact
A diretiva @check verifica se os campos especificados estão presentes nos resultados da consulta. Uma expressão Common Expression Language (CEL) é usada para testar valores de campo. O comportamento padrão da diretiva é verificar e rejeitar nós cujo valor seja null ou [] (listas vazias).
A diretiva @redact encobre uma parte da resposta do cliente. Os campos omitidos ainda são avaliados quanto a efeitos colaterais (incluindo mudanças de dados e @check), e os resultados ainda estão disponíveis para etapas posteriores em expressões CEL.
Usar @check, @check(message:) e @redact
Um dos principais usos de @check e @redact é pesquisar dados relacionados para decidir se determinadas operações devem ser autorizadas, usando a pesquisa na lógica, mas ocultando-a dos clientes. Sua consulta pode retornar mensagens úteis para o processamento correto no código do cliente.
Para fins de ilustração, o campo de consulta a seguir verifica se um solicitante tem uma função de "administrador" adequada para ver os usuários que podem editar um filme.
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 saber mais sobre as diretivas @check e @redact em verificações de autorização,
consulte a discussão sobre a pesquisa de dados de autorização.
Usar @check para validar chaves
Alguns campos de mutação, como _update, podem não fazer nada se um registro com uma chave especificada não existir. Da mesma forma, as pesquisas podem retornar nulo ou uma lista vazia. Esses
não são considerados erros e, portanto, não acionam rollbacks.
Para evitar esse resultado, teste se as chaves podem ser encontradas usando a diretiva @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")
}
Usar a vinculação response para encadear mutações de várias etapas
A abordagem básica para criar registros relacionados, por exemplo, um novo Movie e
uma entrada MovieMetadata associada, é:
- Chamar uma mutação de
_insertparaMovie - Armazene a chave retornada do filme criado
- Em seguida, chame uma segunda mutação
_insertpara criar o registroMovieMetadata.
Mas com Data Connect, é possível processar esse caso comum em uma única
operação de várias etapas acessando os resultados do primeiro _insert no
segundo _insert.
Criar um app de avaliação de filmes de sucesso dá muito trabalho. Vamos acompanhar nossa lista de tarefas com um novo exemplo.
Use response para definir campos com valores do servidor
Na seguinte mutação da lista de tarefas:
- A vinculação
responserepresenta o objeto de resposta parcial até o momento, que inclui todos os campos de mutação de nível superior antes do atual. - Os resultados da operação
todoList_insertinicial, que retorna o campoid(chave), são acessados mais tarde emresponse.todoList_insert.idpara que possamos inserir imediatamente um novo item da lista de tarefas.
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,
})
}
Use response para validar campos usando @check
O response também está disponível em @check(expr: "..."), então você pode usá-lo para
criar uma lógica do lado do servidor ainda mais complicada. Combinado com query { … } etapas
em mutações, você pode fazer muito mais sem viagens de ida e volta
adicionais entre cliente e servidor.
No exemplo a seguir, observe que @check já tem acesso a response.query porque um @check sempre é executado após a etapa a que está anexado.
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 mais informações sobre a vinculação response, consulte a
referência da CEL.
Entenda as operações interrompidas com @transaction e query @check
Mutações de várias etapas podem encontrar erros:
- As operações de banco de dados podem falhar.
- A lógica de consulta
@checkpode encerrar operações.
O Data Connect recomenda que você use a diretiva @transaction com
suas mutações de várias etapas. Isso resulta em um banco de dados mais consistente e em resultados de mutação mais fáceis de processar no código do cliente:
- No primeiro erro ou falha de
@check, a operação será encerrada. Portanto, não é necessário gerenciar a execução de campos subsequentes ou a avaliação da CEL. - As reversões são realizadas em resposta a erros de banco de dados ou lógica
@check, resultando em um estado consistente do banco de dados. - Um erro de rollback sempre é retornado ao código do cliente.
Há alguns casos de uso em que você pode optar por não usar o @transaction. Por exemplo, é possível escolher a consistência eventual se precisar de maior capacidade de processamento, escalonabilidade ou disponibilidade. No entanto, você precisa gerenciar seu banco de dados e seu
código do cliente para permitir os resultados:
- Se um campo falhar devido a operações de banco de dados, os campos subsequentes continuarão
a ser executados. No entanto,
@checks com falha ainda encerram toda a operação. - Os rollbacks não são realizados, o que significa um estado de banco de dados misto com algumas atualizações bem-sucedidas e outras com falha.
- Suas operações com
@checkpodem gerar resultados mais inconsistentes se a lógica de@checkusar os resultados de leituras e/ou gravações em uma etapa anterior. - O resultado retornado ao código do cliente vai conter uma combinação mais complexa de respostas de sucesso e falha a serem processadas.
Diretivas para mutações de Data Connect
Além das diretivas usadas para definir tipos e tabelas, o Data Connect fornece as diretivas @auth, @check, @redact e @transaction para aumentar o comportamento das operações.
| Diretiva | Aplicável a | Descrição |
|---|---|---|
@auth |
Consultas e mutações | Define a política de autorização para uma consulta ou mutação. Consulte o guia de autorização e comprovação. |
@check |
Campos query em operações de várias etapas |
Verifica se os campos especificados estão presentes nos resultados da consulta. Uma expressão Common Expression Language (CEL) é usada para testar valores de campo. Consulte Operações de várias etapas. |
@redact |
Consultas | Reduz parte da resposta do cliente. Consulte Operações de várias etapas. |
@transaction |
Mutações | Garante que uma mutação sempre seja executada em uma transação de banco de dados. Consulte Operações de várias etapas. |
Próximas etapas
Talvez você se interesse por:
- Gerar mutações para seus apps usando ferramentas de assistência de IA
- Autorizar suas mutações de acordo com o guia de autorização
- Chamando mutações do código do cliente para Web, iOS, Android e Flutter.
- Realizar operações de dados em massa com mutações