GraphQL 服务器规范
本文档的目的是指定 Relay 对 GraphQL 服务器的假设,并通过一个示例 GraphQL 模式来演示这些假设。
目录
前言
Relay 对 GraphQL 服务器做出的两个核心假设是,它提供
- 重新获取对象的机制。
- 连接分页的描述。
这个例子演示了这两个假设。这个例子并不全面,但它的目的是快速介绍这些核心假设,在深入了解库的更详细规范之前提供一些背景。
这个例子的前提是,我们想使用 GraphQL 查询原始星球大战三部曲中的飞船和派系信息。
假设读者已经熟悉 GraphQL;如果不熟悉,GraphQL.js 的自述文件是一个很好的起点。
还假设读者已经熟悉 星球大战;如果不熟悉,1977 年版本的星球大战是一个很好的起点,虽然 1997 年的特别版也能满足本文档的要求。
模式
下面描述的模式将用于演示 Relay 使用的 GraphQL 服务器应该实现的功能。两个核心类型是星球大战宇宙中的派系和飞船,其中一个派系与许多飞船相关联。
interface Node {
id: ID!
}
type Faction implements Node {
id: ID!
name: String
ships: ShipConnection
}
type Ship implements Node {
id: ID!
name: String
}
type ShipConnection {
edges: [ShipEdge]
pageInfo: PageInfo!
}
type ShipEdge {
cursor: String!
node: Ship
}
type PageInfo {
hasNextPage: Boolean!
hasPreviousPage: Boolean!
startCursor: String
endCursor: String
}
type Query {
rebels: Faction
empire: Faction
node(id: ID!): Node
}
对象标识
Faction
和 Ship
都有可以用来重新获取它们的标识符。我们通过根查询类型上的 Node
接口和 node
字段将此功能暴露给 Relay。
Node
接口包含一个字段 id
,它是一个 ID!
。根字段 node
接受一个参数 ID!
,并返回一个 Node
。这两个字段协同工作以允许重新获取;如果我们将该字段中返回的 id
传递给 node
字段,我们将获得该对象。
让我们看看它在实际中的作用,并查询叛军的 ID
query RebelsQuery {
rebels {
id
name
}
}
返回
{
"rebels": {
"id": "RmFjdGlvbjox",
"name": "Alliance to Restore the Republic"
}
}
所以现在我们知道系统中叛军的 ID。现在我们可以重新获取它们
query RebelsRefetchQuery {
node(id: "RmFjdGlvbjox") {
id
... on Faction {
name
}
}
}
返回
{
"node": {
"id": "RmFjdGlvbjox",
"name": "Alliance to Restore the Republic"
}
}
如果我们对帝国做同样的事情,我们会发现它返回一个不同的 ID,我们也可以重新获取它
query EmpireQuery {
empire {
id
name
}
}
产生
{
"empire": {
"id": "RmFjdGlvbjoy",
"name": "Galactic Empire"
}
}
和
query EmpireRefetchQuery {
node(id: "RmFjdGlvbjoy") {
id
... on Faction {
name
}
}
}
产生
{
"node": {
"id": "RmFjdGlvbjoy",
"name": "Galactic Empire"
}
}
Node
接口和 node
字段假设此重新获取的全局唯一 ID。没有全局唯一 ID 的系统通常可以通过将类型与特定于类型的 ID 组合来合成它们,这就是本示例中所做的事情。
我们得到的 ID 是 base64 字符串。ID 被设计为不透明的(传递给 node
上的 id
参数的唯一内容是系统中某个对象的 id
查询的未修改结果),并且对字符串进行 base64 编码是 GraphQL 中一个有用的约定,它提醒查看者该字符串是一个不透明的标识符。
有关服务器应该如何运行的完整详细信息,请参阅 GraphQL 对象标识 最佳实践指南,该指南位于 GraphQL 网站。
连接
星球大战宇宙中一个派系拥有许多飞船。Relay 包含功能,使用标准化方式表达这些一对多关系,使操作一对多关系变得容易。这种标准连接模型提供了切片和分页连接的方法。
让我们以叛军为例,询问他们的第一艘飞船
query RebelsShipsQuery {
rebels {
name
ships(first: 1) {
edges {
node {
name
}
}
}
}
}
产生
{
"rebels": {
"name": "Alliance to Restore the Republic",
"ships": {
"edges": [
{
"node": {
"name": "X-Wing"
}
}
]
}
}
}
它使用了 ships
上的 first
参数将结果集切片为第一个。但是如果我们想对其进行分页呢?在每个边缘,都会公开一个游标,我们可以使用它来进行分页。让我们这次询问前两个,并获取游标
query MoreRebelShipsQuery {
rebels {
name
ships(first: 2) {
edges {
cursor
node {
name
}
}
}
}
}
我们得到
{
"rebels": {
"name": "Alliance to Restore the Republic",
"ships": {
"edges": [
{
"cursor": "YXJyYXljb25uZWN0aW9uOjA=",
"node": {
"name": "X-Wing"
}
},
{
"cursor": "YXJyYXljb25uZWN0aW9uOjE=",
"node": {
"name": "Y-Wing"
}
}
]
}
}
}
请注意,游标是一个 base64 字符串。这是之前模式:服务器提醒我们这是一个不透明的字符串。我们可以将此字符串作为 ships
字段的 after
参数传递回服务器,这将让我们要求在先前结果中最后一个之后再获取三艘飞船
query EndOfRebelShipsQuery {
rebels {
name
ships(first: 3 after: "YXJyYXljb25uZWN0aW9uOjE=") {
edges {
cursor
node {
name
}
}
}
}
}
给我们
{
"rebels": {
"name": "Alliance to Restore the Republic",
"ships": {
"edges": [
{
"cursor": "YXJyYXljb25uZWN0aW9uOjI=",
"node": {
"name": "A-Wing"
}
},
{
"cursor": "YXJyYXljb25uZWN0aW9uOjM=",
"node": {
"name": "Millennium Falcon"
}
},
{
"cursor": "YXJyYXljb25uZWN0aW9uOjQ=",
"node": {
"name": "Home One"
}
}
]
}
}
}
棒极了!让我们继续,获取接下来的四艘飞船!
query RebelsQuery {
rebels {
name
ships(first: 4 after: "YXJyYXljb25uZWN0aW9uOjQ=") {
edges {
cursor
node {
name
}
}
}
}
}
产生
{
"rebels": {
"name": "Alliance to Restore the Republic",
"ships": {
"edges": []
}
}
}
嗯。没有更多飞船了;看来叛军系统中只有五艘飞船。如果能够知道我们已经到达连接的末尾,而无需进行另一个往返验证,那就太好了。连接模型通过一个名为 PageInfo
的类型来公开此功能。所以让我们发出获取飞船的两个查询,但这次要求 hasNextPage
query EndOfRebelShipsQuery {
rebels {
name
originalShips: ships(first: 2) {
edges {
node {
name
}
}
pageInfo {
hasNextPage
}
}
moreShips: ships(first: 3 after: "YXJyYXljb25uZWN0aW9uOjE=") {
edges {
node {
name
}
}
pageInfo {
hasNextPage
}
}
}
}
我们得到
{
"rebels": {
"name": "Alliance to Restore the Republic",
"originalShips": {
"edges": [
{
"node": {
"name": "X-Wing"
}
},
{
"node": {
"name": "Y-Wing"
}
}
],
"pageInfo": {
"hasNextPage": true
}
},
"moreShips": {
"edges": [
{
"node": {
"name": "A-Wing"
}
},
{
"node": {
"name": "Millennium Falcon"
}
},
{
"node": {
"name": "Home One"
}
}
],
"pageInfo": {
"hasNextPage": false
}
}
}
}
因此,在第一个飞船查询中,GraphQL 告诉我们有一个下一页,但在下一个查询中,它告诉我们已经到达连接的末尾。
Relay 使用所有这些功能来构建围绕连接的抽象,使这些连接易于高效地使用,而无需在客户端手动管理游标。
有关服务器应该如何运行的完整详细信息,请参阅 GraphQL 游标连接 规范。
进一步阅读
这部分概述了 GraphQL 服务器规范。有关符合 Relay 的 GraphQL 服务器的详细要求,Relay 游标连接 模型的更正式描述,以及 GraphQL 全局对象标识 模型,所有这些信息都可用。
要查看实现规范的代码,GraphQL.js Relay 库 提供了用于创建节点和连接的辅助函数;该存储库的 __tests__
文件夹包含上面示例的实现,作为该存储库的集成测试。
此页面有用吗?
通过以下方式帮助我们使网站更完善 回答一些简短的问题.