Firebase Data Connect 可让您为通过 Google Cloud SQL 管理的 PostgreSQL 实例创建连接器。这些连接器是查询和突变的组合,用于使用架构中的数据。
使用入门指南介绍了 PostgreSQL 的电影评论应用架构。
该指南还介绍了可部署的管理操作和临时管理操作,包括变更。
- 可部署的 mutation 是指您在连接器中实现以从客户端应用调用的 mutation,具有您定义的 API 端点。Data Connect 将身份验证和授权集成到这些突变中,并根据您的 API 生成客户端 SDK。
- 临时管理突变从特权环境中运行,用于填充和管理表。您可以在 Firebase 控制台中创建并执行这些查询,也可以从使用 Firebase Admin SDK 的特权环境中执行,还可以在使用 Data Connect VS Code 扩展程序的本地开发环境中执行。
本指南将深入探讨可部署的突变。
Data Connect 突变的特征
Data Connect 可让您以预期的方式在 PostgreSQL 数据库中执行基本突变:
- 执行 CRUD 操作
- 使用事务管理多步操作
不过,借助 Data Connect 对 GraphQL 的扩展,您可以实现高级变更,从而打造更快、更高效的应用:
- 使用许多操作返回的键标量来简化对记录的重复操作
- 使用服务器值通过服务器提供的操作填充数据
- 在多步突变操作过程中执行查询以查找数据,从而节省代码行数并减少与服务器的往返次数。
使用生成的字段来实现突变
您的 Data Connect 操作将扩展一组根据架构中的类型和类型关系自动生成的 Data Connect。每当您修改架构时,本地工具都会生成这些字段。
您可以使用生成的字段来实现各种突变,从在单个表中创建、更新和删除单个记录,到更复杂的多表更新。假设您的架构包含 Movie
类型和关联的 Actor
类型。Data Connect 会生成 movie_insert
、movie_update
、movie_delete
字段等。
包含
movie_insert
字段的 mutation
|
使用此字段可创建单个电影。 mutation CreateMovie($data: Movie_Data!) { movie_insert(data: $data) { key } } |
包含
movie_update
字段的 mutation
|
使用此字段按键更新单个电影。 mutation UpdateMovie($myKey: Movie_Key!, $data: Movie_Data!) { movie_update(key: $myKey, data: $data) { key } } |
包含
movie_delete
字段的 mutation
|
使用此字段按键删除单个电影。 mutation DeleteMovie($myKey: Movie_Key!) { movie_delete(key: $myKey) { key } } |
变更的基本要素
Data Connect 变更是指具有 Data Connect 扩展功能的 GraphQL 变更。与常规 GraphQL 变更一样,您可以定义操作名称和 GraphQL 变量列表。
Data Connect 通过自定义指令(例如 @auth
和 @transaction
)扩展了 GraphQL 查询。
因此,以下变异具有:
mutation
类型定义SignUp
操作(变更)名称- 单个变量
$username
操作实参 - 单个指令,即
@auth
- 单个字段
user_insert
。
mutation SignUp($username: String!) @auth(level: USER) {
user_insert(data: {
id_expr: "auth.uid"
username: $username
})
}
每个 mutation 实参都需要一个类型声明,可以是内置类型(如 String
),也可以是自定义的架构定义类型(如 Movie
)。
编写基本变更
您可以开始编写 mutation,以创建、更新和删除数据库中的各个记录。
创建
我们来执行基本创建操作。
# 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
})
}
或更新插入。
# 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"
})
}
执行更新
以下是最新动态。制片人和导演当然希望这些平均评分能保持上升趋势。
movie_update
字段包含一个用于标识记录的预期 id
实参,以及一个可用于在此更新中设置值的 data
字段。
mutation UpdateMovie(
$id: UUID!,
$genre: String!,
$rating: Int!,
$description: String!
) {
movie_update(id: $id,
data: {
genre: $genre
rating: $rating
description: $description
})
}
如需执行多项更新,请使用 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
})
}
将增量、减量、附加和前置操作与 _update
搭配使用
虽然在 _update
和 _updateMany
突变中,您可以明确设置 data:
中的值,但通常更合理的做法是应用递增等运算符来更新值。
为了修改之前的更新示例,假设您想增加特定电影的评分。您可以将 rating_update
语法与 inc
运算符搭配使用。
mutation UpdateMovie(
$id: UUID!,
$ratingIncrement: Int!
) {
movie_update(id: $id, data: {
rating_update: {
inc: $ratingIncrement
}
})
}
Data Connect 支持以下用于字段更新的运算符:
inc
,用于递增Int
、Int64
、Float
、Date
和Timestamp
数据类型dec
用于递减Int
、Int64
、Float
、Date
和Timestamp
数据类型
对于列表,您还可以使用以下方法更新单个值或值列表:
add
用于将项(如果尚未存在)附加到列表类型(矢量列表除外)remove
用于从列表类型(矢量列表除外)中移除所有项(如果存在)append
用于将一个或多个项附加到列表类型(矢量列表除外)prepend
用于将一个或多个项添加到列表类型(Vector 列表除外)的前面
执行删除操作
当然,您可以删除电影数据。电影保护主义者肯定希望尽可能长时间地维护实体胶片。
# Delete by key
mutation DeleteMovie($id: UUID!) {
movie_delete(id: $id)
}
您可以在此处使用 _deleteMany
。
# Multiple deletes
mutation DeleteUnpopularMovies($minRating: Int!) {
movie_deleteMany(where: { rating: { le: $minRating } })
}
针对关系编写变更
了解如何在关系上使用隐式 _upsert
变更。
# Create or update a one to one relation
mutation MovieMetadataUpsert($movieId: UUID!, $director: String!) {
movieMetadata_upsert(
data: { movie: { id: $movieId }, director: $director }
)
}
设计架构以实现高效的变更
Data Connect 提供了两项重要功能,可让您编写更高效的更改并节省往返操作。
键标量是简洁的对象标识符,Data Connect 会根据架构中的关键字段自动组装这些标识符。关键标量与效率有关,可让您通过一次调用即可找到有关数据身份和结构的信息。当您想对新记录执行一系列操作,需要一个唯一标识符来传递给后续操作时,以及当您想访问关系型键以执行其他更复杂的操作时,它们尤其有用。
借助服务器值,您可以让服务器根据 expr
实参中的特定服务器端 CEL 表达式,使用存储的值或可轻松计算的值动态填充表中的字段。例如,您可以定义一个字段,该字段在被访问时会应用时间戳,时间戳使用存储在操作请求 updatedAt: Timestamp!
@default(expr: "request.time")
中的时间。
编写高级变更:让 Data Connect 使用 field_expr
语法提供值
如关键标量和服务器值中所述,您可以设计架构,以便服务器根据客户端请求填充常见字段(例如 id
和日期)的值。
此外,您还可以使用客户端应用在 Data Connect request
对象中发送的数据(例如用户 ID)。
实现变更时,请使用 field_expr
语法来触发服务器生成的更新或从请求中访问数据。例如,如需将存储在请求中的授权 uid
传递给 _upsert
操作,请在 userId_expr
字段中传递 "auth.uid"
。
# 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 })
}
或者,在熟悉的待办事项应用中,创建新的待办事项列表时,您可以传递 id_expr
来指示服务器自动为该列表生成 UUID。
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,
})
}
如需了解详情,请参阅标量参考中的 _Expr
标量。
编写高级突变:多步操作
在许多情况下,您可能需要在一次变更中包含多个写入字段(例如插入)。您可能还希望在执行突变期间读取数据库,以便在执行插入或更新等操作之前查找并验证现有数据。这些选项可节省往返操作,从而节省费用。
借助 Data Connect,您可以通过以下方式在 mutation 中执行多步逻辑:
多个写入字段
变异中的多个读取字段(使用
query
字段关键字)。@transaction
指令,可提供关系型数据库中常见的事务支持。@check
指令,可让您使用 CEL 表达式评估读取的内容,并根据评估结果:- 继续执行由变更定义的创建、更新和删除操作
- 继续返回查询字段的结果
- 使用返回的消息在客户端代码中执行适当的逻辑
@redact
指令,可让您从有线协议结果中省略查询字段结果。CEL
response
绑定,用于存储在复杂的多步骤操作中执行的所有更改和查询的累积结果。您可以访问response
绑定:- 在
@check
指令中,通过expr:
实参 - 使用服务器值(使用
field_expr
语法)
- 在
@transaction
指令
对多步突变的支持包括使用事务进行错误处理。
@transaction
指令强制规定,无论是具有单个写入字段(例如 _insert
或 _update
)还是具有多个写入字段的变更,都必须在数据库事务中运行。
不含
@transaction
的 mutation 会按顺序依次执行每个根字段。该操作会将所有错误显示为部分字段错误,但不会显示后续执行的影响。带有
@transaction
的突变保证完全成功或完全失败。如果事务中的任何字段失败,则整个事务都会回滚。
@check
和 @redact
指令
@check
指令用于验证查询结果中是否存在指定字段。通用表达式语言 (CEL) 表达式用于测试字段值。该指令的默认行为是检查并拒绝值为 null
或 []
(空列表)的节点。
@redact
指令会从客户端的响应中编辑一部分内容。系统仍会评估已遮盖字段的副作用(包括数据更改和 @check
),并且结果仍可在 CEL 表达式中用于后续步骤。
使用 @check
、@check(message:)
和 @redact
@check
和 @redact
的主要用途是查找相关数据,以决定是否应授权执行某些操作,在逻辑中使用查找功能,但对客户端隐藏该功能。您的查询可以返回有用的消息,以便在客户端代码中正确处理。
为便于说明,以下查询字段会检查请求者是否具有适当的“管理员”角色来查看可以修改电影的用户。
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
}
}
}
如需详细了解授权检查中的 @check
和 @redact
指令,请参阅有关授权数据查找的讨论。
使用 @check
验证密钥
如果具有指定键的记录不存在,某些突变字段(例如 _update
)可能会无操作。同样,查找可能会返回 null 或空列表。这些不被视为错误,因此不会触发回滚。
为防范此结果,请使用 @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")
}
使用 response
绑定来链接多步突变
创建相关记录(例如新的 Movie
和关联的 MovieMetadata
条目)的基本方法是:
- 为
Movie
调用_insert
突变 - 存储所创建电影的返回键
- 然后,调用第二个
_insert
突变来创建MovieMetadata
记录。
不过,借助 Data Connect,您可以通过在第二个 _insert
中访问第一个 _insert
的结果,以单个多步操作处理这种常见情况。
开发一款成功的影评应用需要付出大量努力。我们通过一个新示例来跟踪待办事项列表。
使用 response
设置具有服务器值的字段
在以下待办事项列表突变中:
response
绑定表示目前为止的部分响应对象,其中包含当前突变之前的所有顶级突变字段。- 初始
todoList_insert
操作的结果(返回id
[键] 字段)稍后会在response.todoList_insert.id
中访问,以便我们立即插入新的待办事项。
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,
})
}
使用 response
通过 @check
验证字段
response
也适用于 @check(expr: "...")
,因此您可以使用它来构建更复杂的服务器端逻辑。与突变中的 query { … }
步数相结合,您无需任何额外的客户端-服务器往返即可实现更多功能。
在以下示例中,请注意:@check
已经有权访问 response.query
,因为 @check
始终在所附加的步骤之后运行。
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,
})
}
如需详细了解 response
绑定,请参阅 CEL 参考文档。
通过 @transaction
和 query @check
了解中断的操作
多步突变可能会遇到错误:
- 数据库操作可能会失败。
- 查询
@check
逻辑可能会终止操作。
Data Connect 建议您将 @transaction
指令与多步突变搭配使用。这样可确保数据库更加一致,并让客户端代码更轻松地处理突变结果:
- 在出现第一个错误或
@check
失败时,操作将终止,因此无需管理任何后续字段的执行或 CEL 的评估。 - 回滚是为响应数据库错误或
@check
逻辑而执行的,可确保数据库状态一致。 - 系统始终会向客户端代码返回回滚错误。
在某些使用情形下,您可能会选择不使用 @transaction
:例如,如果您需要更高的吞吐量、可伸缩性或可用性,则可以选择最终一致性。不过,您需要管理数据库和客户端代码,以便获得结果:
- 如果某个字段因数据库操作而失败,后续字段将继续执行。不过,失败的
@check
仍会终止整个操作。 - 不执行回滚,这意味着数据库处于混合状态,其中一些更新成功,另一些更新失败。
- 如果您的
@check
逻辑使用上一步骤中的读取和/或写入结果,则使用@check
的操作可能会产生更多不一致的结果。 - 返回给客户端代码的结果将包含更复杂的成功和失败响应组合,需要进行处理。
针对 Data Connect 变更的指令
除了在定义类型和表时使用的指令之外,Data Connect 还提供了 @auth
、@check
、@redact
和 @transaction
指令来增强操作的行为。
指令 | 适用范围 | 说明 |
---|---|---|
@auth |
查询和变更 | 定义查询或突变的授权政策。请参阅授权和证明指南。 |
@check |
多步操作中的 query 字段 |
验证指定字段是否出现在查询结果中。使用通用表达式语言 (CEL) 表达式来测试字段值。请参阅多步操作。 |
@redact |
查询 | 隐去客户端响应中的一部分内容。请参阅多步操作。 |
@transaction |
变更 | 强制执行变更始终在数据库事务中运行。请参阅多步操作。 |
后续步骤
您可能感兴趣的内容: