iT邦幫忙

2017 iT 邦幫忙鐵人賽
DAY 18
4
Modern Web

使用 Modern Web 技術來打造 Native App系列 第 18

Day 18:GraphQL 入門 Part I - 從 REST 到 GraphQL

前言

Facebook 在 2012 年開始在公司內部使用 GraphQL,而在 2015 年 7 月開源並撰寫了正式的規格,開源至今還不算很久,但已經引起了廣泛的討論也有了越來越多的使用者。雖然 GraphQL 很新很潮,但我們是為了解決問題,而不是為了使用新技術而使用新技術,所以在這篇之中會特別說明 REST 的缺點,以及 GraphQL 如何解決這些問題。

GraphQL 簡介

GraphQL 最初是從 Facebook 想要優化手機體驗開始發展而來的查詢語言,要理解它可以先把它想成是一個沒有值的 JSON。

下面這個查詢:

{
  me {
    id
    name
  }
}

可以拿回以下格式的結果:

{
  "me": {
    "id": "chentsulin",
    "name": "C. T. Lin"
  }
}

至於更多的細節會在這篇以及接下來的篇章慢慢提到。

REST

過去十年間,HTTP API 介面設計幾乎為因 Rails 而廣泛為人所知的 REST 所主宰。

REST 利用 GETPOSTPUTDELETE 等等的 HTTP 動詞,搭配 /resouces/resouces/:id 這樣代表資源集合以及個別資源的路徑來作為 API 介面,例如:

GET /users      <- 取得使用者清單
POST /users     <- 新增使用者
GET /users/1    <- 取得 id=1 的使用者
PUT /users/1    <- 修改 id=1 的使用者
DELETE /users/1 <- 刪除 id=1 的使用者

而回應會附帶著合適的 HTTP Status Code,可以清楚知道這次請求的結果,例如:

200 OK
400 Bad Request
404 Not Found

依照著這樣的慣例,開發者都可以很容易了解彼此的介面,設計一致的 API 也變得簡單許多。

REST 的問題

雖然瑕不掩瑜,但 REST 終究不是只有美好的一面,有以下這些不易處理的問題:

  • 參數、回傳值型別不固定
  • 多版本時,前後端常不匹配
  • 會拿多餘的欄位
  • 不容易處理巢狀資源
  • 會不斷成長的 Endpoint 數量

會在下面一一提及:

參數、回傳值型別不固定

HTTP API 傳參數最常見的格式是用 x-form-www-urlencodedjson 或是 form-data,回傳值則是 json 比較多見。

因為 JavaScript 是弱型別的語言,中間不管是在 HTTP 層或是在 Database 層,都有可能被進行了轉型,這還跟使用怎樣的套件或是框架有關。例如,經過了 x-form-www-urlencoded 後,根本分不清楚 id=11 是數字 1 還是字串 "1",如果開發文件也不寫清楚那真的會很頭大。

筆者以前還曾經在跟大陸的某家廠商合作時,發現它 API 回傳的 JSON 裡面有一個值是 "null",當時研究了許久發現不了 bug,最後也只能苦笑:

{
  "data": {
    "video": {
      "link": "null"
    }
  }
}

在 GraphQL 裡面,必須仔細的定義型別,所有的值都必須通過 GraphQL 的 Validation,就不會出現這類的失誤。

多版本時,前後端常不匹配

在 React Conference 上有講者提到 Facebook 在全球有 96 個版本同時上線,這用想的就很令人崩潰。

而我們平常在寫網站服務時,最常見的前後端工程師之間的溝通也很類似:

前端工程師 A:為什麼那個欄位不見了?
後端工程師 B:啊,我把它改名了啊。

這樣的問題雖然細微,卻是很常發生而且致命的。

在 GraphQL 裡面前後端可以共用一個固定的 Schema,確保想要拿的資料能確實拿到。

會拿多餘的欄位

我們用 REST 在拿資料時,通常是拿整個 Resource:

GET /posts/1

拿回:

{
  "author": "chentsulin",
  "title": "Day 18:GraphQL 入門 Part I - 從 REST 到 GraphQL",
  "body": "前言....",
  "comments": []
}

如果我只想要 title,那其他的屬性就浪費了網路的傳輸量,雖然也可以加上 fieldignore 之類參數的方式來限制欄位,但也不是標準的做法。

在 GraphQL 裡面,要查詢的欄位要清楚的列出來,其他欄位就不會包括在回應裡面:

{
  post(id: "1") {
    title
  }
}
{
  "post": {
    "title": "Day 18:GraphQL 入門 Part I - 從 REST 到 GraphQL"
  }
}

不容易處理巢狀資源

REST 的目標是去處理單一資源,處理資源之間的關聯就變成一個灰色地帶。例如,要拿回一篇文章以及它的作者,可能會這樣做:

GET /posts/1 <- 拿到 author id
GET /users/chentsulin

如果把 User 的資訊直接併入 Post 中,雖然可以減少一次的查詢,但又在某些不需要關聯的時候會多做資料庫的查詢。

GraphQL 則是這樣做的,要巢狀幾層都不是問題:

{
  post(id: "1") {
    title
    author {
      id
      name  
      posts {
        title
      }
    }
  }
}

不斷成長的 Endpoint 數量

正規化通常總是不利於效能的,如果想要一次多拿一些東西,避免網路上的往返損耗,可能會長出一堆變種的 Endpoint,例如:

GET /post-with-comments
GET /post-with-comments-and-replies
GET /post-with-comments-and-replies-and-likers

越來越多的 Endpoint 會導致複雜度跟維護難度漸漸增高,也破壞了 REST 原本的設計。

GraphQL 則永遠都只有一個 Endpoint,來處理所有的查詢。

結語

直到 2015 年,GraphQL 已經可以在一天內為 Facebook 處理 2600 億 (260 Billion) 個網路請求,開源至今除了 Facebook,國外已經有 Twitter、Github、Pinterest、Coursera 等等許多公司採用,國內也有 Yoctol (筆者所待的公司)、Appier、Fandora 等等的公司在接觸、使用 GraphQL 的技術。筆者對於 GraphQL 的未來發展是相當樂觀的,尤其是在處理大量關聯資料時,它的優點非常顯而易見,一但用了就很難回去了。

以下是 GraphQL 入門系列的目錄:


上一篇
Day 17:活用第三方的 React Native Module
下一篇
Day 19:GraphQL 入門 Part II - 實作 Schema & Type
系列文
使用 Modern Web 技術來打造 Native App30

尚未有邦友留言

立即登入留言