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
Com o Data Connect, é possível fazer 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 esse 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:
inc
para incrementar os tipos de dadosInt
,Int64
,Float
,Date
eTimestamp
dec
para diminuir os tipos de dadosInt
,Int64
,Float
,Date
eTimestamp
Para listas, também é possível atualizar com valores individuais ou listas de valores usando:
add
para anexar itens se eles ainda não estiverem presentes nos tipos de lista, exceto listas de vetores.remove
para remover todos os itens, se houver, dos tipos de lista, exceto listas de vetoresappend
para anexar itens a tipos de lista, exceto listas de vetoresprepend
para 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
campos nas suas 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 principais e valores do servidor,
você pode projetar seu esquema para que o servidor preencha valores de campos
comuns, como id
s 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 cliente.
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 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
response
da CEL, que armazena os resultados acumulados de todas as mutações e consultas realizadas em uma operação complexa de várias etapas. É possível 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
@transaction
executam 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
@transaction
tê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 é 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
_insert
paraMovie
- Armazene a chave retornada do filme criado
- Em seguida, chame uma segunda mutação
_insert
para 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
response
representa 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_insert
inicial, que retorna o campoid
(chave), são acessados mais tarde emresponse.todoList_insert.id
para que possamos inserir imediatamente um novo item de tarefa.
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: "...")
, para que você possa usá-lo e
criar uma lógica do lado do servidor ainda mais complicada. Combinado com as etapas query { … }
em mutações, é possível 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
@check
pode 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.
Em alguns casos de uso, talvez você opte por não usar @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,
@check
s 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
@check
podem gerar resultados mais inconsistentes se a lógica de@check
usar 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 uma 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