這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。
本文根據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):基礎資料型別 (Elementary Data Type)
也就是說,如果你嘗試返回一個不是標量的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 } } }}
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:
titleurllabels(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的思維方式查看各個對象的串連關係和查詢要求了。