本文根据GitHub开发者文档,整理翻译了GitHub GraphQL API的使用方法,你可以了解到GraphQL的基本概念、GitHub GraphQL API的使用,两个实际的使用案例,以及使用Explorer查询GitHub GraphQL API
今年5月22日,GitHub发文宣布,去年推出的GitHub GraphQL API已经正式可用(production-ready),并推荐集成商在GitHub App中使用最新版本的GraphQL API v4。
相信大家对GraphQL早已不陌生,这一Facebook推出的接口查询语言,立志在简洁性和扩展性方面超越REST,并且已经被应用在很多复杂的业务场景中。GitHub这样描述他们为何对GraphQL青睐有加:
我们为API v4选择GraphQL,是因为它为我们的集成商提供了显著的灵活性。相比于REST API v3,它最强大的优势在于,你能够精确的定义所需要的数据,并且毫无冗余。通过GraphQL,你只需要一次请求就能取到通过多个REST请求才能获得的数据。
在GitHub的开发者文档中有较为完整的GraphQL API v4介绍,本文整理并翻译了其中的部分内容,并按以下章节组织,希望能给入门GraphQL或有兴趣从事GitHub App开发的同学以参考:
- 概念解释
- 使用方法
- 示范案例
- 文档指引
概念解释
GitHub GraphQL API v4在架构上和概念上与GitHub REST API v3有很大不同,在GraphQL API v4的文档中,你会遇到很多新的概念。
Schema
schema定义了GraphQL API的类型系统。它完整描述了客户端可以访问的所有数据(对象、成员变量、关系、任何类型)。客户端的请求将根据schema进行校验和执行。客户端可以通过“自省”(introspection)获取关于schema的信息。schema存放于GraphQL API服务器。
Field
field是你可以从对象中获取的数据单元。正如GraphQL官方文档所说:“GraphQL查询语言本质上就是从对象中选择field”。
关于field,官方标准中还说:
所有的GraphQL操作必须指明到最底层的field,并且返回值为标量,以确保响应结果的结构明白无误
标量(scalar):基本数据类型
也就是说,如果你尝试返回一个不是标量的field,schema校验将会抛出错误。你必须添加嵌套的内部field直至所有的field都返回标量。
Argument
argument是附加在特定field后面的一组键值对。某些field会要求包含argument。mutation要求输入一个object作为argument。
Implementation
GraphQL schema可以使用implement定义对象继承于哪个接口。
下面是一个人为的schema示例,定义了接口 X 和对象 Y :
interface X {
some_field: String!
other_field: String!
}
type Y implements X {
some_field: String!
other_field: String!
new_field: String!
}
这表示对象Y除了添加了自己的field外,也要求有接口X的field/argument/return type.(!代表该field是必须的)
Connection
connection让你能在同一个请求中查询关联的对象。通过connection,你只需要一个GraphQL请求就可以完成REST API中多个请求才能做的事。
为帮助理解,可以想象这样一张图:很多点通过线连接。这些点就是node,这些线就是edge。connection定义node之间的关系。
Edge
edge表示node之间的connection。当你查询一个connection时,你通过edge到达node。每个edgesfield都有一个nodefield和一个cursorfield。cursor是用来分页的。
Node
node是对象的一个泛型。你可以直接查询一个node,也可以通过connection获取相关node。如果你指明的node不是返回标量,你必须在其中包含内部field直至所有的field都返回标量。
基本使用
发现GraphQL API
GraphQL是可自省的,也就是说你可以通过查询一个GraphQL知道它自己的schema细节。
- 查询__schema以列出所有该schema中定义的类型,并获取每一个的细节:
query {
__schema {
types {
name
kind
description
fields {
name
}
}
}
}
- 查询__type以获取任意类型的细节:
query {
__type(name: "Repository") {
name
kind
description
fields {
name
}
}
}
提示:自省查询可能是你在GraphQL中唯一的GET请求。不管是query还是mutation,如果你要传递请求体,GraphQL请求方式都应该是POST
GraphQL 授权
要与GraphQL服务器通讯,你需要一个对应权限的OAuth token。
通过命令行创建个人access token的步骤详见这里。你访问所需的权限具体由你请求哪些类型的数据决定。比如,选择User权限以获取用户数据。如果你需要获取版本库信息,选择合适的Repository权限。
当某项资源需要特定权限时,API会通知你的。
GraphQL 端点
REST API v3有多个端点,GraphQL API v4则只有一个端点:
https://api.github.com/graphql
不管你进行什么操作,端点都是保持固定的。
与 GraphQL 通讯
在REST中,HTTP动词决定执行何种操作。在GraphQL中,你需要提供一个JSON编码的请求体以告知你要执行query还是mutation,所以HTTP动词为POST。自省查询是一个例外,它只是一个对端点的简单的GET请求。
关于 query 和 mutation 操作
在GitHub GraphQL API中有两种操作:query和mutation。将GraphQL类比为REST,query操作类似GET请求,mutation操作类似POST/PATCH/DELETE。mutation mame决定执行哪种改动。
query和mutation具有类似的形式,但有一些重要的不同。
关于 query
GraphQL query只会返回你指定的data。为建立一个query,你需要指定“fields within fields"(或称嵌套内部field)直至你只返回标量。
query的结构类似:
query {
JSON objects to return
}
关于 mutation
为建立一个mutation,你必须指定三样东西:
- mutation name:你想要执行的修改类型
- input object:你想要传递给服务器的数据,由input field组成。把它作为argument传递给mutation name
- payload object:你想要服务器返回给你的数据,由return field组成。把它作为mutation name的body传入
mutation的结构类似:
mutation {
mutationName(input: {MutationNameInput!}) {
MutationNamePayload
}
此示例中input object为MutationNameInput,payload object为MutationNamePayload.
使用 variables
variables使得query更动态更强大,同时他能简化mutation input object的传值。
以下是一个单值variables的示例:
query($number_of_repos:Int!) {
viewer {
name
repositories(last: $number_of_repos) {
nodes {
name
}
}
}
}
variables {
"number_of_repos": 3
}
使用variables分为三步:
- 在操作外通过一个variables对象定义变量:
variables { "number_of_repos": 3 }
对象必须是有效的JSON。此示例中只有一个简单的Int变量类型,但实际中你可能会定义更复杂的变量类型,比如input object。你也可以定义多个变量。
- 将变量作为argument传入操作:
query($number_of_repos:Int!){
argument是一个键值对,键是$开头的变量名(比如$number_of_repos),值是类型(比如Int)。如果类型是必须的,添加!。如果你定义了多个变量,将它们以多参数的形式包括进来。
- 在操作中使用变量:
repositories(last: $number_of_repos) {
在此示例中,我们使用变量来代替获取版本库的数量。在第2步中我们指定了类型,因为GraphQL强制使用强类型。
这一过程使得请求参数变得动态。现在我们可以简单的在variables对象中改变值而保持请求的其它部分不变。
用变量作为argument使得你可以动态的更新variables中的值但却不用改变请求。
示范案例
query 示例
让我们来完成一个更复杂的query。
以下query查找octocat/Hellow-World版本库,找到最近关闭的20个issue,并返回每个issue的题目、URL、前5个标签:
query {
repository(owner:"octocat", name:"Hello-World") {
issues(last:20, states:CLOSED) {
edges {
node {
title
url
labels(first:5) {
edges {
node {
name
}
}
}
}
}
}
}
}
让我们一行一行的来看各个部分:
query {
因为我们想要从服务器读取而不是修改数据,所以根操作为query。(如果不指定一个操作,默认为query)
repository(owner:"octocat", name:"Hello-World") {
为开始我们的query,我们希望找到repository对象。schema校验指示该对象需要owner和name参数
issues(last:20, states:CLOSED) {
为计算该版本库的所有issue,我们请求issue对象。(我们可以请求某个repository中某个单独的issue,但这要求我们知道我所需返回issue的序号,并作为argument提供。)
issue对象的一些细节:
- 根据文档,该对象类型为IssueConnection
- schema校验指示该对象需要一个结果的last或first数值作为argument,所以我们提供20
- 文档还告诉我们该对象接受一个states argument,它是一个IssueState的枚举类型,接受OPEN或CLOSED值。为了只查找关闭的issue,我们给states键一个CLOSED值。
edges {
我们知道issues是一个connection,因为它的类型为IssueConnection。为获取单个issue的数据,我们需要通过edges取得node。
node {
我们从edge的末端获取node。IssueConnection的文档指示IssueConnection类型末端的node是一个issue对象。
既然我们知道了我们要获取一个Issue对象,我们可以查找文档并指定我们想要返回的field:
title
url
labels(first:5) {
edges {
node {
name
}
}
}
我们指定Issue对象的title,url,labels。
labels field类型为LabelConnection。和issue对象一样,由于labels是一个connection,我们必须遍历它的edge以到达连接的node:label对象。在node上,我们可以指定我们想要返回的label对象field,在此例中为name。
你可能注意到了在这个Octocat的公开版本库Hellow-World中运行这个query不会返回很多label。试着在你自己的有label的版本库中运行它,你就会看到差别了。
mutation 示例
mutation往往需要你先通过执行query获取请求信息。本示例有两个操作:
- 通过query获取issue ID
- 通过mutation给issue添加一个emoji表情
query FindIssueID {
repository(owner:"octocat", name:"Hello-World") {
issue(number:349) {
id
}
}
}
mutation AddReactionToIssue {
addReaction(input:{subjectId:"MDU6SXNzdWUyMzEzOTE1NTE=",content:HOORAY}) {
reaction {
content
}
subject {
id
}
}
}
不可能在执行一个query的同时执行一个mutation,反之亦然。
让我们看一遍这个示例。目标看起来很简单:给一个issue添加一个emoji表情。
那么我们怎么知道首先需要一个query的?我们目前还不知道。
因为我们想要改动服务器上的数据(给issue添加一个emoji),我们首先从schema中查找有用的mutation。文档显示有addReaction这一mutation,描述为: Adds a reaction to a subject. ,很好!
该mutation的文档列出了三个input field:
- clientMutationId (String)
- subjectId (ID!)
- content (ReactionContent!)
!表明subjectId和content是必需的。content是必需的很好理解:我们要添加表情,肯定要指明使用哪个emoji。
但是为什么subjectId也是必需的?因为subjectId是标明要给哪个版本库中的哪个issue添加表情的唯一方式。
这就是为什么在示例中首先要有一个query:为的是获取ID。
让我们一行一行的来看这个query:
query FindIssueID {
这里我们执行一个query,我们将它命名为 FindIssueID。给query命名是非必需的。
repository(owner:"octocat", name:"Hello-World") {
我们通过查询 repository 对象并传入 owner 和 name 两个argument来指定版本库。
issue(number:349) {
我们通过查询 issue 对象并传入 numberargument来指定所要添加表情的issue。
id
这就是我们从 https://github.com/octocat/Hello-World/issues/349获取并作为subjectId的传递的id。
当我们执行这个query,就能得到 id: MDU6SXNzdWUyMzEzOTE1NTE=
注意:从query中返回的id就是我们将要在mutation中作为subjectID传递的值。文档和schema自省都不会指明这个关系;你需要明白这些名称背后的概念才能弄清楚。
知道了ID,我们就可以进行mutation了:
mutation AddReactionToIssue {
这里我们执行一个mutation,并将它命名为 AddReactionToIssue。和query一样,为mutation起名也是非必需的。
addReaction(input:{subjectId:"MDU6SXNzdWUyMzEzOTE1NTE=",content:HOORAY}) {
让我们来看这一行
- addReaction 是mutation的名称。
- input 是所需的argument键. 对于mutation来说这个键总是 input 。
- {subjectId:"MDU6SXNzdWUyMzEzOTE1NTE=",content:HOORAY} 是所需的argument值。这个值总是由input field(此例中为subjectId 和 content )组成的input object(所以要加大括号)。
我们怎么知道content中用哪个值? addReaction 文档告诉我们contentfield类型为ReactionContent,它是一个枚举类型,因为GitHub的issue只支持部分emoji表情。文档列出了允许的值(注意部分值与对应的emoji名称不同):
- THUMBS_UP
- THUMBS_DOWN
- LAUGH
- HOORAY
- CONFUSED
- HEART
请求中剩下的部分由payload对象组成。在这里我们指明当执行mutation之后我们希望服务器返回的数据。这些行是从addReaction文档中获取的,可返回三个field:
- clientMutationId (String)
- reaction (Reaction!)
- subject (Reactable!)
在此示例中,我们返回两个必须的field(reaction和subject),他们都有必需的内部field(分别是content和id)。
当我们执行mutation,返回结果如下:
{
"data": {
"addReaction": {
"reaction": {
"content": "HOORAY"
},
"subject": {
"id": "MDU6SXNzdWUyMTc5NTQ0OTc="
}
}
}
}
最后还有一点:当你在input object中传递mutation field时,语法可能会很呆板。将field放到variable中可以改善这一点。以下是如何用variable重写原来的mutation:
mutation($myVar:AddReactionInput!) {
addReaction(input:$myVar) {
reaction {
content
}
subject {
id
}
}
}
variables {
"myVar": {
"subjectId":"MDU6SXNzdWUyMTc5NTQ0OTc=",
"content":"HOORAY"
}
}
你可能注意到了在之前的例子中的content(被直接在mutation中使用)没有在HOORAY外面加引号,但在variable中使用时则有引号。原因如下:
- 当在mutation中直接使用content时,schema希望的值类型是ReactionContent,它是枚举类型而不是字符串。如果你加了引号在枚举类型外面的话,schema会抛出错误,因为引号代表了字符串。
- 当在variable中直接使用content时,variables内容必须是有效的JSON,所以引号是必须的。当在执行过程中variable被传入mutation时,schema校验可以正确地解释为 ReactionContent 类型。
文档指引
在GitHub开发者网站中,有网页版的接口文档以供开发者查询,但我不推荐大家通过这种方式查询接口。
传统的REST API作为服务器资源或数据库数据的映射,其结构可理解为一种列表的形式,故通过文档目录可方便的进行查询。GraphQL API从名字就可以看出其内部采用的是一种类似“图”的数据结构,以反映纷繁复杂的节点之间的关系。故很难通过从头至尾阅读文档的方式全面的了解接口。
GitHub为开发者提供了一个名为Explorer的工具:
通过开发者自己的GitHub账号授权,你可以使用这个工具方便的测试GitHub GraphQL API的各种请求。同时右侧的边栏可以让你方便的搜索或链接到所需的API文档。通过这种实验和文档相结合的方式,你就可以按照GraphQL的思维方式查看各个对象的连接关系和查询要求了。
有疑问加站长微信联系(非本文作者)