GraphQL 初探—面向將來 API 及其生態圈

什麼是 GraphQL ?第一次看到這個名詞未免讓人聯想到數據庫查詢語言 SQL 。但本質上,這是兩個徹底不一樣的東西, GraphQL 在官方文檔裏的定義以下:前端

GraphQL is a query language for your API, and a server-side runtime for executing queries by using a type system you define for your data.react

即 GraphQL 既是一個 API 查詢語言,也指其服務端實現。但 GraphQL 不僅是爲了在 API 領域搞個相似數據庫的查詢語言,它的誕生更涉及到 API 設計的思路轉變。git

REST 模式的難題

一般,一項新技術的產生都會伴隨着兩個背景,一個是該技術所在的領域出現了新趨勢、二是原有的技術難以應對新趨勢。而近幾年, API 領域有幾個趨勢愈發值得關注:github

首先是日益增多的移動端應用,和移動端性能自己較低下的矛盾,要求數據加載過程更高效。web

再者,要知足客戶端和前端快速開發、快速添加特性的需求, API 必須能快速拓展。算法

第三則是各類不一樣的前端框架和平臺層出不窮,然後端 API 服務面對衆多的前端框架、乃至前端和客戶端共享 API 的狀況,其可否按需提供數據,會影響接口複用度和開發效率。數據庫

而現現在在 API 領域被普遍使用的 REST 模式,面對上述愈發複雜的客戶端和服務端交互,問題也漸漸浮現:編程

首先是接口靈活性差。因爲設計接口粒度較粗或歷史遺留緣由,接口中有時會存在當前數據交互不須要的字段,致使取到無用且多餘的數據;而另外一方面,有時前端須要一份數據,卻須要手動訪問多個接口才能完整獲取。segmentfault

第二是接口操做流程繁瑣,回想下前端獲取數據的過程,一般咱們要先構造 HTTP 請求,而後接收和解析服務端響應。有時還要對收到的或處理後的數據另做本地數據轉儲,最後才進行 UI 展現。後端

第三,隨着客戶端功能拓展,服務端不斷增長接口。這樣維護衆多接口,不只服務端維護成本高,此外也不能按需提供數據、阻礙了客戶端的快速迭代和拓展。

還有 REST 模式實質上是基於 HTTP 協議的,這雖讓其易於被 Web 開發人員理解和上手,但也決定它不能靈活選擇網絡協議來解決問題。

GraphQL 的解決方案

面對 REST 模式的上述不足, Facebook 提出了他們的解決方案 – GraphQL :

前面提到 GraphQL 既是一個 API 查詢語言,也指其服務端實現,因此 GraphQL 自己也由兩部分組成,Facebook 將它們分別開源

咱們來逐條瞭解下 GraphQL 的特性:

聲明式的數據獲取

以下圖所示,聲明式的數據查詢帶來了接口的精確返回,服務器會按數據查詢的格式返回一樣結構的 JSON 數據、真正照顧了客戶端的靈活性:

另外,這種數據獲取方式也帶來更簡潔的數據查詢流程。 GraphQL 認爲,客戶端只需描述查詢結構發起查詢,再把服務端響應數據用於 UI 展現便可。中間構造請求和轉儲數據的操做能夠交由 GraphQL 客戶端輔助完成。

一個服務僅暴露一個 GraphQL 層

上圖是一個 GraphQL 應用的基本架構,其中客戶端只和 GraphQL 層進行 API 交互,而 GraphQL 層再日後接入各類數據源。這樣一來,只要是數據源有的數據, GraphQL 層均可以讓客戶端按需獲取,沒必要專門再去定接口了。

傳輸層無關、數據庫技術無關

帶來了更靈活的技術棧選擇,好比咱們能夠選擇對移動設備友好的協議,將網絡傳輸數據量最小化,實如今網絡協議層面優化應用。

GraphQL 接入概覽

既然 GraphQL 有諸多優勢,那又該如何接入呢?大致上,有三種接入的方式:

直連數據庫的GraphQL服務

最爲簡潔的服務配置,直接操做數據庫也能減小中間環節的性能損耗。

集成現有服務的GraphQL層

這種配置適合於舊服務的改造,尤爲是在涉及第三方服務時、依然能夠經過原有接口進行交互。

直連數據庫和集成服務的混合模式

前兩種方式的混合:

GraphQL 核心概念淺析

GraphQL 的一大特色即是聲明式的 API Schema ,GraphQL 的 Schema 是一個聲明式的查詢規範(可認爲是服務器和客戶端間的一個查詢協議),它主要由兩部分組成:

  • 類型系統
  • 編寫語法:SDL(視圖定義語言)

GraphQL 的類型系統包含了各編程語言中通用的一些數據類型,具體可參考規範文檔瞭解。

接下來簡單介紹下 GraphQL 的 SDL 語法:

定義 API Schema

自定義類型的定義主要是在服務端完成的,語法以下:

type 類型名 {
    字段名: 類型
}
複製代碼

此外, GraphQL 還有 Query, Mutation, Subscription 等特殊的根類型,用於定義 API Schema 。咱們能夠定義一個用戶:

type User {
    id: Int!
    name: String
}
複製代碼

而後定義幾個用於數據操做的 API Schema :

type Query { // 基本查詢 Schema
    user(id: Int!): User // 傳入一個 id ,返回具體用戶
}

type Mutation { // 操做數據的 Schema
    createUser( // 傳入用戶名自動建立一個用戶
        name: String
    ): User
}

type Subscription { // 監聽數據變動的 Schema
    userChanged: User
}
複製代碼

數據操做

有了這些定義好的 API Schema ,咱們就能夠此來發起數據操做了。 GraphQL 的數據操做也分爲 Query, Mutation, Subscription 三種類型。簡單來說, Query 就是獲取數據的基本查詢;Mutation 支持對數據的增、刪、改等操做;而 Subscription 則用於監聽數據變更、並靠 Websocket 等協議推送變更的消息給訂閱方。

基於前面的定義的用戶 Schema ,咱們能夠寫出以下的數據操做:

query {
  user(id:3) { // 查詢用戶 id 爲3的用戶
    name
  }
}

mutation {
  createUser(name: "Tom") { // 新增一個名爲 "Tom" 的用戶
    name
    id
  }
}

subscription {
  userChanged { // 監聽用戶數據變更
    name
    id
  }
}
複製代碼

上面這些查詢,根字段以後的全部內容稱爲查詢的 payload 。服務端會按查詢格式,在 data 字段返回 payload 中指定的數據,好比 createUser 這個操做就會返回以下的數據:

{
  "data": {
    "createUser": {
      "name": "Tom",
      "id": 9
    }
  }
}
複製代碼

GraphQL 生態圈

經過 API Schema,咱們既可指定 API 功能、同時也能定義客戶端如何請求數據。但前面介紹的只是個規範,而這個 GraphQL 的規範又是如何落地實現的呢?接下來會圍繞服務端、客戶端、調試工具,介紹下 GraphQL 應用開發的 「生態圈」。

服務端實現

在服務端, GraphQL 服務器可用任何可構建 Web 服務器的語言實現。除 JavaScript 以外, Ruby , Python , Scala , Java , Clojure , Go 和 .NET 都有實現供參考。

服務端查詢執行的核心算法也很簡單:就是查詢逐字段遍歷,併爲各字段執行一個 resolver 以處理數據操做。下圖舉了一個例子:

最左邊爲一個 GraphQL 查詢,該語句查詢了 id 爲 'abc' 的做者全部文章的標題和內容。中間一副圖展現了每一個查詢字段對應的數據類型,而後在最右邊可看到每一個字段的解析過程:首先查詢 id 爲 'abc' 的做者,再從該做者處獲取其全部文章;而因爲文章是一個列表,最後咱們還要遍歷這個列表以獲取各文章對應的標題、內容。

這個逐字段解析的流程清晰易懂,但若是服務器只是這麼實現的話,就會面臨性能問題。見下圖的例子,若用戶要查詢文章列表下各個做者的信息,因爲文章列表中可能有大量重複的做者,當處理到同一做者的文章時就要重複查詢該做者信息,甚至當「查詢做者信息」這操做自己就包含大量子操做的話、對服務器性能的消耗就很是可觀:

對這種一個查詢觸發大量相同的數據操做的問題,一種解決思路是將數據操做改成批量處理。仍是用上面的例子,下圖中咱們把查詢做者信息的操做改成存入一個隊列,待合適的時機再批量發起查詢,這時查詢的數量就只是隊列裏的一個最小子集,避免了重複操做。 Facebook 推出的 DataLoder 就是一個這樣的數據批量處理和緩存的方案。

上面討論了 GraphQL 服務端的基本實現思路,而針對 Node.js 的實現,我基於前文示例中的 API Schema 寫了一個簡單的 Demo ,讀者可瞭解下 GraphQL 的服務端具體是如何實現和使用的。

客戶端實現

常見的 GraphQL 客戶端庫有:

  • Relay:Facebook 官方的 GraphQL 客戶端,它大大優化了性能,但只能在 Web 上可用
  • Apollo:一個開源社區項目,旨在爲全部開發平臺(Web, 安卓, iOS , React Native 等)構建強大而靈活的 GraphQL 客戶端

至於如何使用這兩個客戶端庫,能夠參考官方文檔,這裏再也不贅述。而對於 Apollo 的入門, Full-stack React + GraphQL Tutorial 一文提供了深刻淺出的示例,建議動手嘗試下,構建本身第一個 GraphQL 應用吧。

開發工具

GraphQL 有大量實用的開發工具,基本都是基於 introspection 查詢實現的。所謂 introspection 查詢,就是指客戶端向服務器詢問 API Schema 信息的查詢。好比,咱們能夠經過查詢 __schema 等元字段來獲取完整的類型信息:

query {
  __schema {
    types {
      name // 獲取根字段名
      fields {
        name // 獲取字段名
      }
    }
  }
}
複製代碼

有了這樣一個查詢 Schema 信息的功能,就使得 GraphQL 的文檔瀏覽器,自動補全,代碼生成等開發工具很是容易實現。而開發工具中,最有名的就是 GraphiQL 了,其本質上可認爲是個 GraphQL 客戶端,但配有編輯、自動補全、文檔瀏覽等功能,經常使用於服務端的調試。

前面咱們那個服務端 Demo 也以中間件形式引入了基於 GraphiQL 的調試工具 GraphQL PlayGround 。運行 Demo 後,你能夠訪問 localhost:3000/playground 試試上面列舉的全部查詢~

GraphQL 存在的問題

固然, GraphQL 也不是天衣無縫的,如今 GraphQL 主要存在安全性和服務端緩存能力兩方面的問題。

安全問題

GraphQL 聲明式的的數據查詢提供了靈活、易拓展的接口;但若是咱們發起的一次查詢包含了過多的數據操做,那麼這一次查詢就會給數據服務器的帶來巨大的壓力,提高了被 DDOS 的風險。

此外,每次發起的查詢語句,實質上也反映了查詢文檔的結構,若是被攻擊者截取了咱們的請求、拼湊出完整的接口內容,這也不利於接口的安全。

面對查詢壓力,咱們能夠經過服務端限流、客戶端限流等措施來進行緩解,具體限流的措施可參見這篇文章

而對於 API 結構公開傳輸的問題,有人提出一個持久化查詢的方案。簡單來說,就是客戶端和服務端分別將約定好查詢內容轉換爲查詢ID,轉而使用查詢ID進行查詢。這樣一來既解決了查詢語句公開傳輸的問題,而只傳 ID 還順便減小了傳輸的數據量、提高了傳輸速度。

服務端緩存能力

GraphQL 能讓客戶端靈活地請求數據,這就樣一來客戶端請求內容就是不肯定的,服務端難以根據同一個鏈接來維護查詢緩存。

關於這個問題,前面提到 Facebook 有一個 DataLoader 的技術,可用於實現查詢的批量處理和緩存,但其文檔中描述的緩存也只是針對單個請求進行、粒度仍是較粗。

總結

GraphQL 做爲一個新的 API 標準,經過聲明式的數據獲取方式,給客戶端提供了簡潔、靈活、高效的數據查詢。適應了移動互聯網時代客戶端技術的快速發展和需求的快速迭代,是當前 REST 模式的有力競爭者。

同時其活躍的社區和日漸成熟的生態圈也證實了這是一個頗有生命力的技術,目前 GraphQL 已被許多的公司( Facebook , GitHub , Twitter 等等)用於生產環境中,相信其將來還有很大的發展前景。

但 GraphQL 自身存在的安全性等問題也不容忽視;此外引入 GraphQL 勢必存在學習成本,在 API 設計思想上的變化頁還會影響到相應的開發模式、開發流程。因此只有權衡好引入成本和收益,才能讓這項新技術用在刀刃上。

Ref

官方文檔
[譯] 怎樣使用GraphQL(文中架構圖引用自該教程)

相關文章
相關標籤/搜索