天天看點

帶你讀《GraphQL學習指南》之三:GraphQL查詢語言第3章

點選檢視第一章 點選檢視第二章

第3章

GraphQL查詢語言

在距GraphQL開源45年前,一位名叫埃德加 "科德(Edgar M. Codd)的IBM程式員發表了一篇内容不多、名字卻很長的論文—“A Relational Model of Data for Large Shared Databanks”。此文雖然标題平淡無奇,其中表達的思想卻驚天動地。它概述了使用表格存儲和操作資料的模型。此後不久,IBM開始開發可以使用結構化英文查詢語言(Structured English Query Language,SEQUEL)進行查詢的關系型資料庫,這就是後來的SQL。

SQL(Structured Query Language,結構化查詢語言)是特定的語言,用于通路、管理和操作資料庫中的資料。SQL引入了使用單個指令通路多個記錄的概念。它還可以使用任意鍵(key)來通路記錄,而不僅局限于ID。

SQL可以運作的指令非常精簡:SELECT、INSERT、UPDATE和DELETE。這就是你對資料所能做的一切。使用SQL,可以編寫一個查詢語句,它可以跨資料庫中的多個資料表傳回相應的資料。

資料隻能被增加、删除、更改和查詢的想法确實符合表述性狀态傳遞方式,它要求我們根據這四種基本資料操作使用不同的HTTP方法:GET、POST、PUT和DELETE。但是,指定通過REST讀取或更改資料類型的唯一方法是通過端點URL,而非實際的查詢語言。

最初我們開發GraphQL的想法是用于查詢資料庫并将其應用于網際網路。單次的GraphQL查詢就可以傳回相關聯的資料。與SQL一樣,你可以使用GraphQL查詢來更改或删除資料。畢竟,SQL中的QL和GraphQL中的QL代表了相同的東西:查詢語言(Query Language)。

盡管都是查詢語言,但GraphQL和SQL卻完全不同。它們适用于完全不同的環境。我們将SQL查詢語句發到資料庫,而GraphQL查詢語句則發到API。SQL資料存儲在資料表中。GraphQL資料則可以存儲在任何地方:單個資料庫、多個資料庫、檔案系統、RESTful API、WebSockets,甚至是其他GraphQL API。SQL是一種資料庫的查詢語言,而GraphQL則是一種網際網路查詢語言。

GraphQL和SQL的文法也完全不同。GraphQL使用查詢(Query)字段去請求資料而非SELECT。查詢字段是我們使用GraphQL操作的核心。不同于INSERT、UPDATE或者DELETE,GraphQL将所有這些資料整合成為一種資料類型:變更(Mutation)。因為GraphQL是為網際網路而生的,是以它具有訂閱功能,可以用來監聽socket連接配接上的資料變動。SQL可不具備訂閱功能。

GraphQL是依照規範進行标準化的。使用哪種語言并不重要:隻要是GraphQL查詢字段就行了。無論你的項目使用的是JavaScript、Java、Haskell或是其他什麼語言,查詢字段基本一緻。

查詢字段僅僅是發送到GraphQL端的POST請求體中的字元串。下面是一個普通的GraphQL查詢字段:

{

allLifts {

name           

}

你可以使用curl将該查詢字段發送到GraphQL端:

curl '

http://snowtooth.herokuapp.com/

'

-H 'Content-Type: application/json'

--data '{"query":"{ allLifts {name }}"}'

假設GraphQL schema支援這種形式的查詢,你将直接收到響應的JSON。JSON中包含你在名為data的字段中請求的資料,或者出錯的時候傳回的報錯資訊。流程很簡單,送出請求,收到響應。

如果要更改資料,我們可以發送變更(mutation)。變更和查詢字段頗為相似,而它是用來修改資料的,類似于SQL的UPDATE。執行更改所需要的資料可以和變更一起直接發送,請看下面的例子:

mutation {

setLiftStatus(id: "panorama" status: OPEN) {

name
status           

上面的變更是采用GraphQL查詢語言編寫的,可以假設它想要把id為panorama的lift的狀态改為OPEN。同樣,可以使用curl将該操作發送到GraphQL伺服器:

curl '

--data '{"query":"mutation {setLiftStatus(id: "panorama" status: OPEN) {name status}}"}'

随後将詳細介紹将變量映射到查詢或變更中的更好的方法。在本章中,我們将重點介紹如何使用GraphQL來構成查詢(query)、變更(mutation)和訂閱(subscription)。

GraphQL API工具

GraphQL社群已經開發了幾個和GraphQL API互動的開源工具。它們可以幫助你通過GraphQL查詢語言編寫查詢,發送到GraphQL端擷取傳回的JSON。下面我們将介紹兩種最常用的針對GraphQL API的GraphQL查詢字段測試工具:GraphiQL和GraphQL Playground。

GraphiQL

GraphiQL是由Facebook建立的浏覽器内的內建開發環境(IDE),友善開發人員查詢和浏覽GraphQL API。GraphiQL提供文法高亮顯示、代碼完成和錯誤警告,它使你可以在浏覽器中直接運作并檢視查詢結果。許多公共API提供了GraphiQL接口,你可以使用該接口查詢實時資料。

它的操作界面相當簡單。在其中編寫查詢,點選運作按鈕便可在右側面闆中看到回報結果,如圖3-1所示。

帶你讀《GraphQL學習指南》之三:GraphQL查詢語言第3章

我們的查詢流程從使用GraphQL查詢語言編寫的文本開始。我們将此文本稱為查詢文檔。将查詢文本寫入左側面闆。在GraphQL文檔中可以定義一個或多個操作,比如查詢、變更或是訂閱。圖3-2顯示了如何向文檔中添加查詢操作。

帶你讀《GraphQL學習指南》之三:GraphQL查詢語言第3章

單擊播放按鈕進行查詢。在右側面闆中會将到JSON格式的響應結果(見圖3-3)。

帶你讀《GraphQL學習指南》之三:GraphQL查詢語言第3章

點選右上角打開Docs視窗,這裡顯示了與目前服務互動所需了解的全部内容。該文檔會自動添加到GraphiQL,因為它是從服務的schema中讀取的。schema定義了服務上可用的資料,并且GraphiQL通過對schema進行自檢查詢來自動建構文檔。在此期間,你始終可以在文檔資料總管中檢視它,如圖3-4所示。

帶你讀《GraphQL學習指南》之三:GraphQL查詢語言第3章

通常來說,你是通過GraphQL服務主機本身的URL來通路GraphiQL的,如果你建構自己的GraphQL服務,那麼可以增加一個路由來渲染GraphQL接口,以便你的使用者可以浏覽公開的資料。當然,你還可以下載下傳獨立版本的GraphiQL。

GraphQL Playground

另一個浏覽GraphQL API的工具是GraphQL Playground。由Prisma團隊開發的GraphQL Playground除了具有GraphiQL的功能之外,還添加了一些有趣的配置項。通路

https://www.graphqlbin.com

,可以最快體驗GraphQL Playground。隻要你提供接口,就可以通過它和資料進行互動。

GraphQL Playground和GraphiQL非常相似,并且還有一些讓你用起來很友善的額外功能。其中最重要的就是可以通過GraphQL請求發送自定義的HTTP請求頭,如圖3-5所示(我們将在第5章介紹授權時更詳細地讨論該功能)。

GraphQL Bin也是一款很友善的協作工具,你可以和其他人共享你的Bin連結,見圖3-6。

帶你讀《GraphQL學習指南》之三:GraphQL查詢語言第3章
帶你讀《GraphQL學習指南》之三:GraphQL查詢語言第3章

GraphQL Playground有桌面版本,你可以使用Homebrew在本地安裝:

brew cask install graphql-playground

或者你可以直接從網站

http://bit.ly/graphql-pg-releases

下載下傳。

在安裝或了解了GraphQL Bin後,就可以開始發送查詢(Query)了。要快速上手,可直接在Playground網站上粘貼API(公共API或本地運作的localhost項目均可)。

公共GraphQL API

從零開始練習GraphQL的最好辦法就是使用公共API來發送查詢。有一些公司提供了GraphiQL接口,你可以使用它來查詢公共資料:

SWAPI (

https://graphql.github.io/swapi-graphql/

)

這是本書開頭曾經提過的Facebook項目—星球大戰API。

GitHub API (

https://developer.github.com/v4/explorer/

作為最大的公共GraphQL API之一,GitHub GraphQL API允許你發送查詢和變更,以檢視和更改GitHub上的實時資料。當然,你需要登入GitHub才能享受該服務。

Yelp (

https://www.yelp.com/login?return_url=%2Fdevelopers%2Fgraphiql

Yelp維護了一個可以使用GraphiQL查詢的GraphQL API。當然你需要建立一個Yelp開發賬号才行。

更多公共GraphQL API的示例參見(

https://github.com/APIs-guru/graphql-apis

)。

GraphQL查詢字段

假定有一個名為Snowtooth的滑雪勝地,這裡的Web團隊正在使用GraphQL提供關于纜車和雪道狀态的實時資訊。雪地摩托巡邏隊可以通過手機直接開關纜車和雪道。本章的示例請參考Snowtooth的GraphQL Playground接口(

http://snowtooth.moonhighway.com

你可以使用查詢操作從API請求資料。查詢字段就是你希望從GraphQL伺服器擷取的資料。在發送查詢時,會按字段請求資料。你從伺服器接收到的JSON資料響應中的相同字段會自動進行映射。例如,如果你發送allLifts查詢字段請求名稱和狀态,那麼便會收到一個JSON響應,其中包含allLifts的數組以及每個電梯name(名稱)和status(狀态)的對象字元串,如下所示:

query {

name
status           

錯誤處理

成功的查詢将傳回一個JSON文檔,它會包含“data”鍵。如果查詢失敗,則會傳回一個包含“errors”鍵的JSON文檔,錯誤資訊會作為值。JSON中是可以同時包含“data”鍵和“errors”鍵的。

你可以向查詢文檔添加多個查詢,但是一次隻能運作一個操作。例如,你可以在查詢文檔中放置兩個查詢操作。

query lifts {

name
status           

query trails {

allTrails {

name
difficulty           

當按下“play”按鈕時,GraphQL Playground将會要求你在兩個操作之間選擇一個。如果你想在一個請求中查詢所有資料,那麼就需要将全部字段放在同一個查詢中:

query liftsAndTrails {

liftCount(status: OPEN)

name
status           
name
difficulty           

這就是GraphQL開始展現優勢的時候。可以在一次查詢中接收各種不同的資料。我們通過status: OPEN查詢狀态為開啟的纜車的數量liftCount,它為我們提供了相應的資料。我們還可以查詢每個電梯的name和status。最後查詢每條雪道的name和difficulty。

Query是GraphQL類型。我們稱其為根類型(root type),因為它可以将操作進行映射,而操作代表了查詢文檔的根節點。我們已經在API schema中定義了可以查詢的GraphQL字段。文檔将告訴我們可以在查詢類型上選擇哪些字段。

根據文檔,我們了解到當查詢此API時,可以選擇的字段有liftCount、allLifts和allTrails。它也定義了更多字段可供選擇,但是查詢的關鍵就在于我們可以自主選擇需要哪些字段以及省略哪些字段。

編寫查詢時,把需要的字段用大括号括起來。這些塊被稱為選擇集(selection set)。在選擇集内定義的字段和GraphQL類型直接相關。查詢類型中已經定義了liftCount、allLifts和allTrails。

選擇集之間可以互相嵌套。因為allLifts字段傳回了Lift類型的清單,是以我們需要使用大括号以在此類型上建立新的選擇集。與纜車相關的各種資料都是可請求的,在此例中我們僅請求纜車的name和status。同樣,allTrails查詢字段也傳回對應的Trail類型。

傳回的JSON包括了我們在查詢中請求的所有資料。資料的格式和我們的查詢格式一緻。每個JSON字段的名稱也和我們選擇集中的字段相同。可以通過指定别名來更改查詢結果中傳回對象的字段名稱,如下所示:

open: liftCount(status: OPEN)

chairlifts: allLifts {

liftName: name
status           

skiSlopes: allTrails {

name
difficulty           

響應如下所示:

{

"data": {

"open": 5,
"chairlifts": [
  {
    "liftName": "Astra Express",
    "status": "open"
  }
],
"skiSlopes": [
  {
    "name": "Ditch of Doom",
    "difficulty": "intermediate"
  }
]           

現在我們把資料恢複到相同的結構,然後把其中幾個字段重新命名。傳入查詢參數(query argument)可對GraphQL查詢結果進行篩選。參數是與查詢相關聯的鍵值對。如果我們想要獲知處于關閉狀态的纜車的名字,可以發送一個參數來過濾響應:

query closedLifts {

allLifts(status: "CLOSED", sortBy: "name") {
  name
  status
}           

還可以使用參數來選擇資料。舉例來說,假設我們想要查詢單個纜車的狀态,那麼可以傳入唯一辨別符:

query jazzCatStatus {

Lift(id: "jazz-cat") {
  name
  status
  night
  elevationGain
}           

這樣我們就能夠看到傳回的“Jazz Cat”纜車資料,包括name(名稱)、status(狀态)、night(夜間運作)和elevationGain(爬高)。

邊和連接配接

在GraphQL查詢語言中,字段可以是标量類型或對象類型。标量類型與其他語言中的基本類型類似,是選擇集的“葉子”,開箱即用,GraphQL帶有5種内置的标量類型:整數(Int)、浮點數(Float)、字元串(String)、布爾值(Boolean)和唯一辨別符(ID)。整數和浮點數都傳回JSON數字,字元串和ID 都傳回JSON字元串。布爾值類型隻傳回布爾值。盡管ID和String将傳回相同類型的JSON資料,但是GraphQL仍然確定ID傳回唯一的字元串。

GraphQL對象類型是schema中定義的一個或多個字段的組,定義了應當傳回的JSON對象的形式。JSON可以在字段下無限嵌套對象,GraphQL也可以。可以通過查詢一個對象和相關對象的詳細資訊将對象連接配接在一起。

例如,假設我們想要一份雪道清單,以便乘坐指定的纜車:

query trailsAccessedByJazzCat {

Lift(id:"jazz-cat") {
    capacity
    trailAccess {
        name
        difficulty
    }
}           

在前面的查詢中,需要一些關于“Jazz-Cat”纜車的資料。我們的選擇集包括了對capacity字段的請求。capacity是标量類型,它傳回一個整數,該整數表示一次可以乘坐的人數。trailAccess字段的類型是Trail(對象類型)。在這個例子中,trailAccess傳回的是一份經過篩選的纜車清單,可通過jazz-cat進行通路。由于trailAccess是Lift類型中的一個字段,是以API可以使用父對象jazz-cat Lift的詳細資訊對纜車清單進行篩選。

這個操作查詢了兩種類型資料(lifts和trails)之間的一對多連接配接。一台纜車和許多雪道相關聯。如果從Lift節點開始周遊圖形,便可通路到一個或多個Trail節點,這些節點通過一條名為trailAccess的邊連接配接到Lift節點。如果有人覺得這裡的圖是無向圖,那麼我們就從Trail節點周遊到Lift節點,如下所示:

query liftToAccessTrail {

Trail(id:"dance-fight") {
    groomed
    accessedByLifts {
        name
        capacity
    }
}           

在liftToAccessTrail查詢中,我們選擇了一條名為dance-fight的雪道(Trail)。groomed字段傳回了一個布爾值标量類型,我們可以據此判斷纜車是否正在維護。而accessedByLifts字段傳回了正在将滑雪愛好者送往dance-fight雪道的纜車。

片段

GraphQL查詢文檔可以定義操作和片段。片段(fragment)是可以在多個操作中重用的選擇集,如下所示:

Lift(id: "jazz-cat") {
  name
  status
  capacity
  night
  elevationGain
  trailAccess {
    name
    difficulty
  }
}
Trail(id: "river-run") {
  name
  difficulty
  accessedByLifts {
    name
    status
    capacity
    night
    elevationGain
  }
}           

這次查詢請求jazz-cat纜車和river-run雪道的問題。該纜車的選擇集内包括了name、status、capacity、night和elevationGain字段。我們期望擷取的river-run雪道的資訊和纜車類型有重複的部分。這樣一來,我們便能夠建立一個片段來減少查詢中的備援:

fragment liftInfo on Lift {

name

status

capacity

night

elevationGain

你可以使用fragment(片段)辨別符來建立片段。片段是針對特定類型的選擇集,是以必須在其定義中包含和每個片段相關聯的類型。本例中的片段名為liftInfo,它是Lift類型上的一個選擇集。

當我們想要将liftInfo片段字段添加到另一個選擇集時,可以通過三點文法和片段名稱來實作,具體代碼如下:

Lift(id: "jazz-cat") {
  ...liftInfo
  trailAccess {
    name
    difficulty
  }
}
Trail(id: "river-run") {
  name
  difficulty
  accessedByLifts {
    ...liftInfo
  }
}           

這個文法和ES6的擴充運算符相似,目的也差不多,都是将一個對象的鍵和值配置設定給另一個對象。三點文法表示将片段中的字段配置設定給目前選擇集。在本例中,我們可以使用一個片段在查詢中的兩個不同位置選擇name、status、capacity、night和elevationGain。

我們無法将liftInfo片段添加到Trail選擇集中,因為它隻定義了Lift類型上的字段。我們可以為Trail添加另一個片段:

Lift(id: "jazz-cat") {
  ...liftInfo
  trailAccess {
    ...trailInfo
  }
}
Trail(id: "river-run") {
  ...trailInfo
  groomed
  trees
  night
}           

fragment trailInfo on Trail {

difficulty

accessedByLifts {

...liftInfo           

在這個例子中,我們建立了一個名為trailInfo的片段,并在兩個查詢中進行了引用。我們還使用了trailInfo片段中的liftInfo片段來選擇與纜車相關的詳細資訊。你可以根據需要建立任意數量的片段并交替使用它們。在river-run雪道查詢所使用的選擇集當中,我們把片段同與river-run雪道相關的其他細節結合了起來。你既可以把片段和選擇集當中的其他字段組合使用,也可将同一類型的多個片段組合在一個選擇集當中:

...trailStatus
...trailDetails           

fragment trailStatus on Trail {

fragment trailDetails on Trail {

groomed

trees

使用片段的一個好處就是,可以通過修改一個片段來同時修改許多個用于不同查詢的選擇集:

對liftInfo片段中選擇集的更改減少了所有使用該片段的每個查詢所選擇的資料。

聯合類型

我們已經看過如何傳回對象清單,但截至目前,所傳回的都是單一類型的清單。如果希望清單傳回不止一種類型,那麼可以建立聯合類型(union type),它可以把兩個不同的對象類型關聯起來。

假設我們正在為大學生開發一款可以安排日程的應用程式。學生可以将鍛煉身體和學習活動添加到日程中,示例網址為:

https://graphqlbin.com/v2/ANgjtr

如果你查閱GraphQL Playground的文檔,将會發現AgendaItem字段是一個聯合類型,這意味着它可以傳回多個類型。具體來說,它應該可以傳回Workout或StudyGroup,畢竟這些是大學生日程安排的一部分。

在編寫學生日程查詢時,可以使用片段來定義選擇哪些字段,采用字元串“on”來分别指定選擇條件:

query schedule {

agenda {
...on Workout {
  name
  reps
}
...on StudyGroup {
  name
  subject
  students
}           

響應如下:

"data": {

"agenda": [
  {
    "name": "Comp Sci",
    "subject": "Computer Science",
    "students": 12
  },
  {
    "name": "Cardio",
    "reps": 100
  },
  {
    "name": "Poets",
    "subject": "English 101",
    "students": 3
  },
  {
    "name": "Math Whiz",
    "subject": "Mathematics",
    "students": 12
  },
  {
    "name": "Upper Body",
    "reps": 10
  },
  {
    "name": "Lower Body",
    "reps": 20
  }
]           

這裡,我們使用的是内聯片段(inline fragment)。内聯片段沒有名稱屬性,可以直接在查詢中将選擇集配置設定給特定類型。當聯合字段傳回不同類型的對象時,可以通過它來定義選擇哪些字段。對于每個Workout,該查詢都會傳回它的name和reps。而對StudyGroup,則傳回name、subject和students。這樣傳回的資料就根據查詢項目的不同而有所差異了。

還可以使用具名片段去查詢聯合類型:

query today {

agenda {
...workout
...study           

fragment workout on Workout {

reps

fragment study on StudyGroup {

subject

students

接口

在處理由單個字段傳回的多個對象類型時,可以使用接口(interface)。接口是一種抽象類型,它建構的是在類似對象類型中應實作的字段清單。當另一種類型部署該接口時,它便會将來自該接口的所有字段包括進來,通常還包括一些它自己的字段。如果你想檢視這個示例,請通路GraphQL Bin(

https://www.graphqlbin.com/v2/yoyPfz

當你檢視文檔中的agenda字段時,可以看到它傳回了ScheduleItem接口。該接口定義的字段包括:name、start和end。實作ScheduleItem接口的任何對象類型都需要部署這些字段。

文檔還告訴我們,StudyGroup和Workout類型實作了此接口。這意味着我們可以放心地假設這兩種類型都有name、start和end字段:

agenda {

name
start
end           

schedule查詢似乎對agenda字段是否傳回多個類型毫不在意。它隻需要項目的name、start和end字段,以便建立該學生的日程安排表。

在查詢接口時,還可以在傳回特定對象類型時使用片段來選擇其他字段:

name
start
end
...on Workout {
  reps
}           

該schedule查詢已修改為當ShecduleItem為Workout時額外請求reps字段。

變更

到目前為止,我們已經讨論了很多關于讀取資料的内容。查詢代表了發生在GraphQL中所有資料讀取的操作。那麼為了寫入資料,我們将會使用變更(mutation)關鍵字。變更和查詢相類似,它擁有名稱(name)屬性,還擁有可以傳回對象類型或标量的選擇集。不同之處在于,變更對資料的更改會影響後端的資料狀态。

例如,下面這種變更就屬于“從删庫到跑路”類型的:

mutation burnItDown {

deleteAllData

Mutation是一個根對象類型。API schema定義了該類型上可用的字段。前面示例中的API就是通過部署deleteAllData字段删除用戶端的所有資料,并傳回一個标量類型:如果傳回了布爾值true,那就說明所有的資料删除成功了。反之,如果傳回了false,則說明可能哪裡出了問題。是否實際删除資料還是由API來處理,這一點我們将在第5章中進一步讨論。

讓我們考慮另一個變更的示例,與其搞破壞,不如進行創造:

mutation createSong {

addSong(title:"No Scrubs", numberOne: true, performerName:"TLC") {

id
title
numberOne           

這是一個建立新歌的變更。歌曲的title、numberOne狀态和performerName作為參數發送,我們可以假設這次變更将這首新歌添加到了資料庫中。如果變更字段傳回一個對象,那麼需要在變更的末尾添加一個選擇集。在這種情況下,當變更執行完成後,将傳回一個Song(歌曲)類型,包括剛剛建立的歌曲詳細資訊,帶有id、title、numberOne字段,如下所示:

"addSong": {
  "id": "5aca534f4bb1de07cb6d73ae",
  "title": "No Scrubs",
  "numberOne": true
}           

之前的例子展示了變更操作可能産生的反應。如果出現了什麼問題,會傳回JSON格式的錯誤,而非我們建立的Song對象。

也可以使用變更來改變現有的資料。假設我們想要改變Snowtooth纜車的狀态,具體代碼如下:

mutation closeLift {

setLiftStatus(id: "jazz-cat", status: CLOSED) {
 name
 status           

這樣我們便把jazz-cat纜車的狀态從打開切換為關閉。變更之後,可在選擇集中檢視最新變更結果,進而得知更改後的纜車名稱和狀态。

使用查詢變量

到目前為止,我們已經通過将新的字元串作為變更參數來修改資料。既然如此,當然也可以輸入變量。使用變量來替換查詢中的靜态值,這樣我們便能夠按照需求傳遞參數了。我們來看看addSong變更,把靜态字元串換成變量。在GraphQL中,用“$+變量名”來表示變量:

mutation createSong($title:String!, $numberOne:Int, $by:String!) {

addSong(title:$title, numberOne:$numberOne, performerName:$by) {

id
title
numberOne           

靜态值被替換成了“$+變量名”。接下來,我們使用參數名來映射每個$變量名稱。在GraphiQL或Playground中,有一個查詢變量的視窗,會将我們輸入的資料作為JSON對象進行發送。請務必使用正确的變量名稱作為JSON的鍵:

"title": "No Scrubs",

"numberOne": true,

"by": "TLC"

變量在發送參數資料時非常有用,不僅讓變更在測試中更有組織性,而且在連接配接用戶端界面時,允許動态輸入也會非常友善。

訂閱

GraphQL可以提供的第三種操作是訂閱(subscription)。有時用戶端可能希望從伺服器推送實時的更新。訂閱功能允許我們監聽GraphQL API以進行實時的資料更改。

GraphQL中的訂閱功能來自Facebook中的真實用例。開發團隊需要一種方法來顯示一個貼子在不重新整理頁面的情況下獲得的實時點贊數量。實時點贊是一個由訂閱支援的實時用例。每個用戶端都訂閱了點贊事件,進而能夠看到點贊數量的實時變化。

同變更和查詢一樣,訂閱也是根類型。在訂閱類型中,用戶端可以監聽API schema中定義的資料的更改。編寫用于監聽的GraphQL查詢字段也和定義其他操作相類似。

例如,通過訂閱功能,我們可以監聽任何纜車的狀态變化:

subscription {

liftStatusChange {

name
capacity
status           

當開始運作訂閱時,我們通過WebSocket監聽纜車狀态的變更。注意,單擊GraphQL Playground中的Play按鈕不會立即傳回資料。當訂閱發送到伺服器時,将啟動對任何資料更改的監聽。

要看看推送到訂閱的資料,需要改變一些資料。我們需要打開一個新視窗或頁籤,以便輸入變更來觸發更改。在GraphQL Playground中運作了訂閱操作之後,就不要在同一個視窗或頁籤中進行操作了。如果你使用GraphiQL編寫訂閱,隻需打開第2個浏覽器視窗連接配接到GraphiQL接口即可。

在新視窗或頁籤裡,發送更改纜車狀态的變更:

setLiftStatus(id: "astra-express", status: HOLD) {
  name
  status           

當運作這個變更時,Astra Express的狀态将會改變,它的name、capacity和status字段将被推送到我們的訂閱當中。

讓我們再次更改一個纜車的狀态。這一次我們試着将Whirlybird纜車的狀态設定為關閉。請注意,這份新的資訊已經傳遞到我們的訂閱。GraphQL Playground允許你檢視兩組響應資料以及将資料推送到訂閱的時間。

不同于查詢和變更,訂閱是保持開放的。每次纜車狀态發生變化時,新的資料都會推送到該訂閱。若要停止監聽纜車狀态的更改,你需要取消訂閱。在GraphQL Playground中按下停止按鈕即可。在GraphiQL中則隻能關閉訂閱所在的浏覽器頁籤。

自檢

GraphQL最強大的功能之一就是自檢(inrospection)。自檢是查詢目前API schema細節的能力。自檢是将GraphQL文檔添加到GraphiQL Playground接口的方式。

你可以向每個GraphQL API發送查詢,這些查詢傳回關于API schema的資料。例如,如果我們想了解在Snowtooth中哪些GraphQL類型可用,可以通過運作__schema查詢檢視這些資訊。

__schema {

types {
  name
  description
}           

當運作這個查詢時,可以看到API上所有可用的類型,包括根類型、自定義類型,甚至是标量類型。如果我們想要檢視特定類型的詳細資訊,可以運作__type進行查詢,并将想要查詢的類型名稱作為參數進行發送:

query liftDetails {

__type(name:"Lift") {

name
fields {
  name
  description
  type {
    name
  }
}           

該自檢查詢向我們展示了可用于Lift(纜車)類型上查詢的所有字段。在遇見新的GraphQL API時,最好通過此方法找出根類型上可用的字段:

query roots {

queryType {
  ...typeFields
}
mutationType {
  ...typeFields
}
subscriptionType {
  ...typeFields
}           

fragment typeFields on __Type {

fields {

name           

自檢查詢遵循GraphQL查詢語言的規則。前面查詢的備援部分已經通過使用片段進行了優化。我們正在查詢類型的名稱以及每個根類型下可用的字段。自檢功能可以幫助用戶端了解目前API schema的工作方式。

抽象文法樹

抽象文法樹(Abstract Syntax Tree,AST)是一種表示查詢的分層結構對象,它包含了GraphQL查詢詳細資訊的嵌套字段。查詢文檔是一個字元串。當我們向GraphQL API發送查詢時,該字元串将會被解析為抽象文法樹,并在操作之前進行驗證。

這個過程的第一步是将字元串解析為一組更小的片段,具體來說就是将關鍵字、參數甚至方括号和冒号解析為一組單獨的标記。這個過程被稱為詞法分析(lexical analysis)。接下來,将詞法分析解析成抽象文法樹。AST形态的查詢字段更容易修改和驗證。

例如,你的查詢從GraphQL文檔開始。文檔至少包含一個定義(definition),但也可包含一個定義清單。定義隻能是OperationDefiniation或FragmentDefinition中的一種。下面是一個包含了三個定義的文檔示例:兩個操作和一個片段。

Lift(id: "jazz-cat") {
  name
  night
  elevationGain
  trailAccess {
    name
    difficulty
  }
}           

mutation closeLift($lift: ID!) {

setLiftStatus(id: $lift, status: CLOSED ) {

...liftStatus           

fragment liftStatus on Lift {

一個OperationDefinition隻能包含三種操作類型(mutation、query、subscription)中的一種。每個操作定義都包含了OperationType(操作類型)和SelectionSet(選擇集)。

每個操作後面的大括号裡面就是本次操作的選擇集。這些是我們要查詢的實際字段和參數。例如,Lift字段是jazzCatStatus查詢的選擇集,而setLiftStatus字段表示closelift變更的選擇集。

選擇集之間是可以嵌套的。jazzCatStatus查詢包含3個嵌套選擇集。第一個選擇集包含Lift字段,接着嵌套了一個包含name、night、elevationGain和trailAccess字段的選擇集,再接着又嵌套了另一個包含name和difficulty字段的選擇集。

GraphQL可以周遊這個AST并根據GraphQL語言和目前schema對其詳細資訊進行驗證。如果查詢語言的文法正确并包含了我們請求的字段和類型,則執行操作;否則将傳回一個特定錯誤。

此外,這個AST對象比字元串更容易修改。如果我們想在jazzCatStatus查詢中追加打開的纜車數量,可以直接修改AST,僅需在操作中添加一個額外的選擇集即可。AST是GraphQL的重要部分。每個操作都被解析為AST,以便驗證并最終執行。

在本章中,你了解了GraphQL查詢語言。現在我們可以使用這種語言來和GraphQL服務互動了。但是,如果沒有特定GraphQL服務可用的操作和字段的具體定義,這一切都不可能實作。這個特定的定義就是我們之前曾數次提到過的GraphQL schema,下一章就是它的主場。