跳至主要内容
版本: v18.0.0

GraphQL 服务器规范

本文档的目的是指定 Relay 对 GraphQL 服务器的假设,并通过一个示例 GraphQL 模式来演示这些假设。

目录

前言

Relay 对 GraphQL 服务器做出的两个核心假设是,它提供

  1. 重新获取对象的机制。
  2. 连接分页的描述。

这个例子演示了这两个假设。这个例子并不全面,但它的目的是快速介绍这些核心假设,在深入了解库的更详细规范之前提供一些背景。

这个例子的前提是,我们想使用 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
}

对象标识

FactionShip 都有可以用来重新获取它们的标识符。我们通过根查询类型上的 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__ 文件夹包含上面示例的实现,作为该存储库的集成测试。


此页面有用吗?

通过以下方式帮助我们使网站更完善 回答一些简短的问题.