05.25 GraphQL,高效、結構化與系統化的 REST 替代方案

在提及web API的時候,我們大多都會想到REST(表述性狀態傳遞,Representational State Transfer)。你發送請求到一個特定請求的URL,然後你會收到結果,就像是HTML,XML,JSON,明文,PDF,JPEG…這些應用可以理解的任何格式。

Facebook的web API系統,GraphQL,提供了一種新的定義API的方式。開發人員使用強類型查詢語言來定義請求和響應,這可以讓一個應用精確地指定它需要從API中獲取哪些數據。因此GraphQL的目的是提供一個更高效,更結構化,更系統的方式來替代REST。

本文我們會展示GraphQL與REST的不同之處,這些不同是如何影響API設計的,以及在從服務器獲取數據方面GraphQL為什麼是比REST更好的選擇。

GraphQL 對比 REST

使用REST,您一般會通過一個特製的URL來提交您的請求,並將每種請求發送到不同的終點——例如:/movie/2120 或者 /director/5130。

使用GraphQL,您可以在一個類似JSON的查詢中為您正在搜索的數據提交一個陳述性請求,並且所有的請求都會發往同一個終點。用於請求的schema決定了您將得到的數據返回。這是一個標準化的、自我描述的方法來請求所需的具體數據,並且只是所需的數據。

不同的請求類型使用不同的schema,替代了使用不同終點的URL格式,使得查詢機制更加靈活。雖然像Swagger這樣的很多REST API也符合通用規範,沒有規則說REST APT必須通過Swagger生成。GraphQL默認為API提供了一個形式定義。

從某種意義上來說,GraphQL有點像SQL(結構化查詢語言)。使用SQL提供的數據源,您可以將所有數據請求連接到一個公共終點,並且請求的格式決定了您將得到什麼樣的記錄返回。雖然SQL有許多不同的實現,但是SQL查詢語法在這些實現之間保持高度一致。

GraphQL 查詢

GraphQL使用一個schema或者一個數據定義來描述在查詢和響應中如何組織要檢索的數據。任何使用ORM(對象關係映射器)的人都應該熟悉GraphQL的數據schema定義。

type Movie {
id: ID
title: String
released: Date
director: Director
}
type Director {
id: ID
name: String
movies: [Movie]
}

您會注意到在schema中的每個元素都有一個類型定義。GraphQL有自己的查詢類型系統,用於驗證傳入的schema並返回與定義格式一致的數據。

提交給GraphQL的查詢類似通過一個schema定義:

type Query {
movie(id: ID!): Movie
director(id: ID): Director
}

我們這有一個查詢需要最多兩個參數,movie(通過它的ID號)和director(也通過ID)。

類型旁邊的“!”表示這是查詢中一個強制元素。換句話說,您必須通過ID查詢movie,director是可選項。強制元素也可以使用在數據schema中。

這是使用上面的schema來定義的查詢格式的一種可行方法:

query GetMovieByID ($id: ID!) {
movie(id: $id) {
name
}
}

這個查詢使用了一個必需的單獨定義變量($id),通過ID號碼查找movie並返回它的name。需要注意的是,GraphQL查詢可以通過字段內嵌套的數組來返回相關對象和字段,而不是隻能返回單個字段。

像這樣的查詢變量在查詢的一個單獨的部分傳遞,使用如下格式:

{
“id”: 23}

GraphQL 類型

GraphQL的查詢類型系統說明了許多常見的標量類型,像strings和integers。大多數查詢將通過他們重寫。但是這些類型系統也包括了幾種用於更復雜查詢工作的高級類型。

  • interface類型能用來創建一個預定義字段集合的抽象類型,其他的類型可以實現和複用。

  • union類型允許從單一類型的查詢中穿過多種類型返回不同類型的結果。

  • input類型能用來傳遞上述類型的整個對象作為查詢參數,前提是這些對象是用可驗證的常用標量類型創建的。

如果您使用interface或者union,您將需要使用內聯片段和指令來返回數據,這些數據基於這些對象類型可指定的條件。

可以在查詢中返回的另一種類型是邊緣類型,在可選邊緣字段返回。邊緣包含節點——實質上是記錄——和遊標,他們是編碼字符串,提供關於如何從該對象向前或者向後翻頁的上下文信息。

{
movie {
name
actors (first:5) {
edges {
cursor
node {
name

}
}
}
}
}

這個例子中,一個movie節點將返回movie的name和actors的name。對於movie中的每個actor,我們都會收到一個節點,這個節點包含actor的name和允許瀏覽actor的“鄰居”的光標。

GraphQL 分佈

處理任何數據源時的常見情況是通過光標請求頁面中的數據。GraphQL提供了多種分頁的方式。

當你請求記錄時,你不僅可以指定要請求的記錄數和起始偏移量,還可以指定如何請求連續頁面。上一節中的示例代碼僅返回與給定影片相關聯的前五位演員 - 和括號中的first:5參數所表示的一樣。

actors之後的first:子句可以緊跟其他描述如何獲取後續項的關鍵字。offset:可用於簡單偏移量,但添加或刪除數據時偏移量可能會被丟棄。

要獲得最健壯的分頁,你需要使用可以與請求的對象一起傳遞的光標,通過使用上述的edge類型。這允許你創建在分頁之間插入或刪除數據時不會被打斷的分頁機制 - 例如,通過將對象的唯一ID用作其他計算的起始鍵索引。

GraphQL的mutations

使用REST API,您可以通過使用POST、PATCH和其他HTTP動作提交請求來改變服務器端的數據。使用GraphGL,您可以使用特定的查詢schema做出改變,一個mutation查詢——再一次,與SQL使用UPDATE或DELETE同樣的方式查詢。

為了改變數據,需要您使用一個叫做mutation的schema來提交GraphQL查詢。

mutation CreateMovie ($title: String!, $released: Date!) {
createMovie (title: $title, released: $released){
id
title
released
}
}
[submitted data]
{
“title”: “Seven Samurai”
“released”: “1950”
}

包括mutation查詢在內的所有的查詢都能返回數據。在例子中,createMovie後面的大括號中的字段列表指定了在使用該請求創建新紀錄之後,從服務器返回的內容。這種情況下,id字段的值將由數據庫創建;其他字段的值將在查詢中提交。

需要記住的另外一件事情,用於返回數據的查詢和數據類型在設計上不同於用於請求數據的查詢和數據類型。Mutation查詢需要校驗傳入的數據,所以這些查詢使用的類型意味著服務該功能。同樣,返回查詢對象中使用的字段用於顯示而不是驗證。如果您從查詢中得到了一個GraphQL對象返回,那它可能帶有循環引用或者其他問題的字段,使得它不能用於查詢參數。

為什麼使用GraphQL

GraphQL查詢具有明確聲明特性是選擇GraphQL而不選擇REST的一個關鍵原因。查詢和返回數據有個正式定義看起來應該是有好處的,不只是為了與API和實現方面保持一致。

正如Phil Sturgeon在他關於GraphQL與REST的考察報告中所指出的那樣,GraphQL字段結構能更容易的使用更細化的版本來查詢。因為會被棄用或者隨著時間流逝是特定字段,而不是整個API版本。GraphQL還可以使用批量版本管理方法; 關鍵是在推出變更時您不會被迫這麼做。

Apollo GraphQL的工程經理Sashko Stubailo說,GraphQL API的開源工具製造商指出了GraphQL方法的另一個優點:自文檔化。Stubailo寫到:“每個可能的查詢、對象和字段都具有名稱、描述和類型信息,可以用標準方法從服務器查詢。”

GraphQL的自文檔性質提供了一種“自我反省”,它意味著您可以使用查詢返回與它自己相關的信息。用這個方法,使用GraphQL查詢來工作的軟件可以不用強制處理特定的字段集合;它能自動推斷出這些字段。

也就是說,GraphQL比較新,REST/Swagger比較老的這個事實不應該成為支持前者的原因。正如《每日API設計》的作者Arnaud Lauret在討論兩個標準時所說的:“一個GraphQL API就像一個REST API一樣,必須是為了達成某個目的而創建,並且從是由外而內的設計而不是由內而外的。”


分享到:


相關文章: