【譯】GraphQL vs. REST

兩種經過 HTTP 發送數據的方式:區別在哪裏?

GraphQL 經常被認爲是一種全新的 API 方式。你能夠經過發送一次查詢請求便得到所須要的數據,而不是經過服務器嚴格定義的請求終端。GraphQL 確實有這樣的變革能力,一個團隊在採用 GraphQL 後可以使得前端和後端的合做變得比以前更流暢。然而在實際操做中,兩種技術都經過發送 HTTP 請求獲取結果,並且 GraphQL 使用了 REST 模型中的不少內建元素前端

那麼從技術層面來說它們的本質究竟是什麼?這兩款 API 範例的類似處和區別都有哪些?我在文章最後將會聲明 GraphQL 和 REST 的區別並非很大,但 GraphQL 其自己的一些小的改變使得爲開發和自定義一個 API 帶來了巨大的區別。react

那麼言歸正傳,咱們會先指出 API 的一些性質,而後咱們會討論 GraphQL 和 REST 是如何處理它們的。android

資源

REST 的核心理念就是資源。每一個資源都由一個 URL 定義,而後經過向指定 URL發送 GET 請求來獲取資源。目前大部分 API 會獲得的一個 JSON 響應。這個請求和響應以下:ios

GET /books/1

{
  "title": "Black Hole Blues",
  "author": {
    "firstName": "Janna",
    "lastName": "Levin"
  }
  // ... more fields here
}複製代碼

注意:在以上實例中,有的 REST APIs 會把 「author」 當成獨立資源返回。git

在 REST 中須要注意的是,資源的類型和你獲取資源的方法是緊密相關的。當使用以上 REST 數據時,你可能會把它當成是 book 的一個終端。github

GraphQL 在這方面就至關不同了,由於在 GraphQL 裏這兩個概念是徹底分開的。在你的模版裏可能會有 ‘Book’ 和 「author」 兩種類型:數據庫

type Book {
  id: ID
  title: String
  published: Date
  price: String
  author: Author
}

type Author {
  id: ID
  firstName: String
  lastName: String
  books: [Book]
}複製代碼

注意在這裏咱們對可得到的數據類型進行了描述,但這個描述並無告訴你每一個對象是如何從客戶端得到的。這就是 REST 和 GraphQL 的核心區別之一 —— 對某一指定資源的描述不必定要和獲取的方式相結合。express

若是想要真正獲得到某一本書或者其做者的信息,咱們須要在咱們現有的模式中創造一個 ‘Query’ 類型:編程

type Query {
  book(id: ID!): Book
  author(id: ID!): Author
}複製代碼

如今咱們能夠發送一個相似於 REST 的請求,不過此次是使用 GraphQL:後端

GET /graphql?query={ book(id: "1") { title, author { firstName } } }

{
  "title": "Black Hole Blues",
  "author": {
    "firstName": "Janna",
  }
}複製代碼

很好,如今咱們有成果了!即便雙方都使用 URL 來發送請求並返回相同的 JSON 結構做爲迴應,咱們仍是能立刻看出 GraphQL 和 REST 之間的區別。

首先,咱們能看出 GraphQL 查詢的 URL 詳細指出了咱們所尋找的資源以及咱們所關心的字段。並且 API 的使用者決定是否須要包括有關 ‘author’ 的資源,而不是由服務器端的代碼來決定。

但最重要的是,資源的身份以及 Book 和 Author 的概念和獲取的方式無關。咱們實際上可使用多種不一樣的請求來獲取同一本書的不一樣字段。

總結

咱們已經找到了一些類似和不一樣的地方:

  • 相同: 都擁有資源這個概念,並且均可以指定資源的身份
  • 相同: 都能經過 HTTP GET 和一個 URL 來獲取信息
  • 相同: 請求的返回值都是 JSON 數據
  • 不一樣: 在 REST 中,你所訪問的終端就是所需對象的身份,在 GraphQL 中,對象的身份和獲取的方式是獨立存在的
  • 不一樣: 在 REST 中,資源的形式和大小是由服務器所決定的。在 GraphQL 中,服務器聲明哪些資源能夠得到,而客戶端會對其所需資源做出請求。

好吧,若是你以前使用過 GraphQL 和/或 REST的話這些看上去很基礎。若是你以前沒用過 GraphQL,你可使用 Launchpad 來試試這個實例 。這是一個用於在瀏覽器中創造和探索 GraphQL 實例的工具。

URL 路徑 vs GraphQL 模版

一款沒法正確預測結果的 API 是沒有實際用途的。當你使用一款 API 的時候,大部分狀況下會把它當作程序的某一部分去使用它,這款程序會知道能夠調用什麼 API,以及 API 的結果是什麼。這樣程序才能運用好 API 返回的結果。

因此一款 API 最重要的一個特色就是去描述它到底能獲得什麼。你在讀 API 文檔的時候偏偏就是爲了瞭解這些。如今經過使用 GraphQL 的內部描述特色或者使用相似 Swagger 這種適用於 REST 模板系統的工具,咱們能夠採用編程的方式來獲取這方面的信息。

目前的 REST API 一般被形容爲一連串的端點:

GET /books/:id
GET /authors/:id
GET /books/:id/comments
POST /books/:id/comments複製代碼

因此你能夠將此 API 的「形態」描述爲線性 —— 由於你能夠接觸一連串的信息。當你想要獲取或者存儲信息的時候,最早想到的問題就是「我應該使用哪個終端」?

而在 GraphQL 中,就像咱們以前提到的,你並非使用一系列 URL 來驗證 API 能夠得到有哪些信息,而是使用 GraphQL 的模板:

type Query {
  book(id: ID!): Book
  author(id: ID!): Author
}

type Mutation {
  addComment(input: AddCommentInput): Comment
}

type Book { ... }
type Author { ... }
type Comment { ... }
input AddCommentInput { ... }複製代碼

將它和 REST 中請求相同數據集的請求路徑作對比時,有幾點有趣的地方。首先,在區分讀取和寫入時,GraphQL 使用的是 Mutation 和 Query 這兩種不一樣的初始類型,而不是經過對同一 URL 發送兩種不一樣的 HTTP 術語。在 GraphQL 文檔中,你可使用關鍵字來選擇你所發送的操做:

query { ... }
mutation { ... }複製代碼

若是想要了解更多有關查詢語言的細節,請閱讀我以前寫的文章, 「對 GraphQL 查詢的分析」。

你能夠看出 Query 類型中的字段和咱們以前所寫的 REST 路徑正好重合。這是由於此類型是咱們數據的切入點,因此這在 GraphQL 中是和終端 URL 幾乎相同的一個概念。

你從 GraphQL API 中獲取最初資源的方式和使用 REST 的方法相似 —— 都是經過傳遞一個名字和一些參數 —— 但最大的不一樣之處是在這以後你會作什麼。你能夠用 GraphQL 發送一個複雜的請求並經過與模板之間的關係來獲取額外的數據。但在 REST 中,你須要經過發送多個請求來使用相關數據去構造最初的迴應,或者在 URL 中包含特殊參數來修改響應的結果。

結論

在 REST 中,可得到數據的空間是由一系列線性的終端來描述的,而在 GraphQL 中是經過使用有關聯的模板:

  • 相同: REST API 中的一列終端和 GraphQL API 中的 Query 和 Mutation 類的字段很像,都是數據的切入點。
  • 相同: 兩種 API 均可以區分數據的讀取和寫入。
  • 不一樣: 在 GraphQL 中,你可使用由模板定義的關係,經過發送一次請求從初始點一直走到相關數據。然而在 REST 中,你必需要使用多個終端來獲取相關資源。
  • 不一樣: 在 GraphQL 中,除了在每一個請求的根源處所能獲取的類型都是 Query 類外,Query 的字段和其餘類的字段沒有本質區別。比方說,你能夠在 Query 的每一個字段裏放一個參數。而在 REST 中,嵌套的URL裏沒有第一類這個概念。
  • 不一樣: 在 REST 中,你經過將 HTTP 術語 GET 改成 POST 來指定寫入,但在 GraphQL 裏須要改變請求裏的關鍵字

因爲第一個類似點,不少人把 GraphQL 的 Query 類中的字段看成「終端」或者「請求」。雖然這的確是一個合理的比較,但這種理解可能會誤導別人認爲 Query 類和其餘類的工做方式不一樣,這種理解是錯誤的。

路徑處理器 vs Resolvers

當你調用一款 API 的時候到底發生了什麼?一般狀況下 API 會在服務器端收到請求後執行一段代碼。這類代碼可能會進行計算,也多是從數據庫中加載數據,甚至會使用另外一款 API 或作其餘事。重要的是你不須要了解它在內部到底作了了什麼。不過 REST 和 GraphQL 這兩款 API 都具有很是標準化的內部執行方式,經過比較它們內部的執行區別,咱們能夠找出這兩款 API 基礎層面的不一樣點。

在接下來的對比中我會使用 JavaScript,由於這是我最熟悉的語言。不過你固然能夠用其餘語言去實現 REST 或者 GraphQL。我會省略設置服務器的步驟,由於這不是重點。

來看看這個用 experss 寫的 hello world 例子,express 是 Node 裏很火的 API庫 之一。

app.get('/hello', function (req, res) {
  res.send('Hello World!')
})複製代碼

咱們首先建立了一個可以返回hello world字串符的/hello 終端。經過這個例子中咱們能夠得知使用 REST API 來寫服務器時一個 HTTP 請求的生命週期:

  1. 服務器接收請求並解析 HTTP 術語 (這個例子中術語爲 ‘GET’)和其 URL
  2. API 庫將術語和路徑相結合並在服務器代碼中找到與之相匹配的函數
  3. 函數運行並返回結果
  4. API 庫將結果序列化與響應代碼和數據頭相結合,最終發送給客戶端

GraphQL 的工做方式極爲類似,對於同一個 hello world 的例子來講二者幾乎相同:

const resolvers = {
  Query: {
    hello: () => {
      return 'Hello world!';
    },
  },
};複製代碼

就像你所看到的,咱們將函數和一個類別中的字段相呼應,爲指定的 URL 提供一個處理函數。在這個例子中,‘hello’ 是 ‘Query’ 中的一個字段。在 GraphQL 中,這種對字段進行操做的函數被稱爲 resolver

咱們須要用 Query 來發送請求:

query {
  hello
}複製代碼

當服務器接收到 GraphQL 的請求會執行如下步驟:

  1. 服務器接收請求並開始解析 GraphQL 的請求
  2. 此 Query 的每一個字段會被仔細分析來找出有哪些 resolver 函數會被使用
  3. 函數運行並返回結果
  4. GraphQL 庫和服務器將返回結果和迴應相結合,最終獲得和 Query 形態相匹配的結果

因此你最終獲得的結果爲:

{ "hello": "Hello, world!" }複製代碼

但這裏有個小技巧,咱們實際上能夠連續訪問字段兩次!

query {
  hello
  secondHello: hello
}複製代碼

在這個例子中出現了相同的生命週期,但因爲咱們使用化名對同一個字段發送了兩次請求,hello 的 resolver 實際上被使用了兩次。這個例子很牽強,但重點是咱們能夠對同一請求中對多個字段進行操做,並且在一個 query 中咱們也能夠對單個字段進行屢次使用。

爲了進行補充,如下是一個嵌套在一塊兒的 resolvers 例子:

{
  Query: {
    author: (root, { id }) => find(authors, { id: id }),
  },
  Author: {
    posts: (author) => filter(posts, { authorId: author.id }),
  },
}複製代碼

這些 resolvers 能夠用來對 query 進行補充:

query {
  author(id: 1) {
    firstName
    posts {
      title
    }
  }
}複製代碼

因此即便這些 resolvers 是平級的,因爲它們能夠和多種類型相結合,你能夠在嵌套的 query 裏將這些 resolvers 連在一塊兒使用。若是想了解 GraphQL 是如何執行工做的,請閱讀如下文章「詳解 Graph QL」

來看看如何使用完整的例子配合不一樣的請求來進行測試!

圖解:對資源進行獲取的 REST 屢次請求 vs GraphQL 的一次請求

結論

最終咱們能夠得知,REST 和 GraphQL API 均可以在網絡中經過不一樣方式使用函數。若是你對如何搭建 REST API 很熟悉,那麼使用 GraphQL API 應該不會很不同。不過 GraphQL 有很大的優點,由於你可使用它去執行多個相關函數,並且全程不須要屢次請求往返。

  • 相同: REST的終端和 GraphQL 的字段都會在服務器端運行函數
  • 相同: 二者本質上都須要依靠框架和庫來使用和處理網絡模板。
  • 不一樣: 在 REST 中,每次請求一般只使用一個路徑處理函數。在 GraphQL 中,同一 Query 可使用多個 resolver 來使用多個資源創造嵌套在一塊兒的迴應。
  • 不一樣: 在 REST 中,你能夠本身創造每一個迴應的形式。在 GraphQL 中,迴應的模式經過 GraphQL 的執行庫來與請求的形式相匹配。

總而言之,你能夠將 GraphQL 當成是能夠在一次請求裏執行多個終端的系統,就像是重複使用的 REST。


這些意味着什麼?

咱們沒法在此文章中對全部細節作出詮釋,好比對象識別、超媒體以及緩存。我之後可能會再討論這些問題,但我想讓你明白的是,經過了解 API 的基本知識點可得知,REST 和 GraphQL 工做時所使用的基礎觀念是十分類似的。

我以爲二者之間的區別反而成爲了 GraphQL 的優點。特別是給予使用者構建多個 resolver 函數的功能很是炫酷,並且也能夠發送一個複雜的請求來一次性獲得多種資源,整個過程是可預測的。這個特色避免了 API 的使用者爲了構建某個迴應形式而去使用多個終端,同時也避免了處理額外不須要的數據。

然而,GraphQL 目前尚未 REST 那麼多的工具和擴展。比方說,你沒法對 GraphQL 的結果使用 HTTP 的緩存方式。但目前社區方面正在努力打造更好的工具和框架,並且你可使用相似 Apollo clientRelay 這類緩存工具。

若是有更多有關對比 REST 和 GraphQL 的想法,請積極留言!


掘金翻譯計劃 是一個翻譯優質互聯網技術文章的社區,文章來源爲 掘金 上的英文分享文章。內容覆蓋 AndroidiOSReact前端後端產品設計 等領域,想要查看更多優質譯文請持續關注 掘金翻譯計劃

相關文章
相關標籤/搜索