- 原文地址:REST 2.0 Is Here and Its Name Is GraphQL
- 原文做者:Michael Paris
- 譯文出自:掘金翻譯計劃
- 譯者: mnikn
- 校對者: CACppuccino,sunui
GraphQL 是一種 API 查詢語言。雖然它和 REST 徹底不一樣,可是 GraphQL 可做爲 REST 的代替品,提供同樣的體驗。對於一個有經驗的開發者來講,它可做爲一個很是強有力的工具。javascript
在這篇文章中,咱們將看看如何用 REST 和 GraphQL 處理一些常見的任務。本文中舉了三個例子,你會看到用於提供熱門電影和演員信息的 REST 和 GraphQL API 的代碼,還有一個簡單的用 HTML 和 jQuery 寫出的前端應用。前端
咱們將會使用這些 API,看看它們在技術上有什麼不一樣點,這樣咱們就能夠知道它們有什麼優點和不足。首先,讓咱們看一下它們所採用了什麼技術。java
早期網絡的技術架構很簡單。早期互聯網上的網頁使用靜態的 HTML 文檔,隨後網站把動態的內容存儲在數據庫(例如:SQL)並使用 JavaScript 來進行交互。大多數網絡的內容是經過桌面電腦上的瀏覽器來瀏覽的,而且看起來一切都運做良好。react
快速前往 2007 年,當時喬布斯在展現 iPhone。智能手機除了對世界各地的文化、交流形成深遠的影響,它還讓開發者的工做變得更加複雜了。智能手機改變了當時開發的模式,在短短几年,咱們忽然間有了臺式機、iPhone、Android 和平板電腦。android
所以,開發者們開始使用 RESTful API 來給各類類型和規模的應用提供數據。新的開發架構看起來像是這樣的:ios
GraphQL 是一種由 Facebook 設計並開源的 API 查詢語言。在構建 API 時,你能夠認爲 GraphQL 是 REST 的替代品。然而 REST 是一個概念上的模型,用來設計並實現你的 API,而 GraphQL 是一種標準的語言,系統地在客戶端和服務端中建立了一個強力的條約。有了這樣一門能與咱們全部的設備通信的語言,能夠有效地簡化建設大規模、跨平臺應用程序的過程。git
經過 GraphQL 咱們的圖解可簡化爲:github
在接下來的教程裏,我建議你跟着代碼看一下!你能夠在 附隨的 GitHub 倉庫 中找到本文的代碼。web
三個項目:數據庫
這些項目都挺簡單,咱們儘量經過這些項目來比較它們之間在技術上的不一樣。
若是你願意的話能夠打開三個終端窗口並 cd
到 RESTful
、GraphQL
和 Client
項目文件夾。在每一個項目的文件夾裏執行命令 npm run dev
來運行開發服務器。一旦你的服務器已準備好,就能夠執行下一步了 :)
咱們的 RESTful API包含了一些路徑:
注意: 咱們簡單的數據模型已經有了 6 個路徑須要維護和記錄。
讓咱們想象一下咱們是客戶端開發者,須要使用電影的 API 來經過 HTML 和 jQuery 構建一個簡單的頁面。爲了構建咱們的頁面,咱們須要有關電影和其出演人員的信息。咱們的 API 有這些功能,因此如今只需獲取其數據。
若是你打開一個終端而且運行命令
curl localhost:3000/movies複製代碼
你獲得的響應會是這樣子的:
[
{
"href": "http://localhost:3000/movie/1"
},
{
"href": "http://localhost:3000/movie/2"
},
{
"href": "http://localhost:3000/movie/3"
},
{
"href": "http://localhost:3000/movie/4"
},
{
"href": "http://localhost:3000/movie/5"
}
]複製代碼
在 RESTful 的風格中,API 會返回一對指向真正電影對象的連接數組。咱們能夠經過運行命令 curl http://localhost:3000/movie/1
來獲取第一個電影的信息,經過命令 curl http://localhost:3000/movie/2
來獲取第二個,以此類推。
若是你看下 app.js
你會發現咱們的用來獲取頁面數據的函數:
const API_URL = 'http://localhost:3000/movies';
function fetchDataV1() {
// 1 call to get the movie links
$.get(API_URL, movieLinks => {
movieLinks.forEach(movieLink => {
// For each movie link, grab the movie object
$.get(movieLink.href, movie => {
$('#movies').append(buildMovieElement(movie))
// One call (for each movie) to get the links to actors in this movie
$.get(movie.actors, actorLinks => {
actorLinks.forEach(actorLink => {
// For each actor for each movie, grab the actor object
$.get(actorLink.href, actor => {
const selector = '#' + getMovieId(movie) + ' .actors';
const actorElement = buildActorElement(actor);
$(selector).append(actorElement);
})
})
})
})
})
})
}複製代碼
你可能注意到,這種狀況不太理想。總體上咱們調用了 1 + M + M + sum(Am)
次 API,其中 M 是電影的數量,sum(Am) 是處理 M 個電影的行爲的數量和。對於數據量小的應用來講還能夠,可是這沒法適用於大型的生產系統。
小結一下,咱們簡易的 RESTful 方法還不可以知足要求。爲了改進咱們的 API,咱們可能須要叫後端團隊構建一個額外的 /moviesAndActors
路徑提供給頁面。一旦這個路徑完成,咱們就能夠經過僅用一次請求來代替 1 + M + M + sum(Am)
次調用。
curl http://localhost:3000/moviesAndActors複製代碼
它返回的數據看起來像這樣:
[
{
"id": 1,
"title": "The Shawshank Redemption",
"release_year": 1993,
"tags": [
"Crime",
"Drama"
],
"rating": 9.3,
"actors": [
{
"id": 1,
"name": "Tim Robbins",
"dob": "10/16/1958",
"num_credits": 73,
"image": "https://images-na.ssl-images-amazon.com/images/M/MV5BMTI1OTYxNzAxOF5BMl5BanBnXkFtZTYwNTE5ODI4._V1_.jpg",
"href": "http://localhost:3000/actor/1",
"movies": "http://localhost:3000/actor/1/movies"
},
{
"id": 2,
"name": "Morgan Freeman",
"dob": "06/01/1937",
"num_credits": 120,
"image": "https://images-na.ssl-images-amazon.com/images/M/MV5BMTc0MDMyMzI2OF5BMl5BanBnXkFtZTcwMzM2OTk1MQ@@._V1_UX214_CR0,0,214,317_AL_.jpg",
"href": "http://localhost:3000/actor/2",
"movies": "http://localhost:3000/actor/2/movies"
}
],
"image": "https://images-na.ssl-images-amazon.com/images/M/MV5BODU4MjU4NjIwNl5BMl5BanBnXkFtZTgwMDU2MjEyMDE@._V1_UX182_CR0,0,182,268_AL_.jpg",
"href": "http://localhost:3000/movie/1"
},
...
]複製代碼
很好!經過單獨一次請求,咱們就可以獲得咱們所需的頁面數據。回頭看下 Client
目錄裏面的 app.js
,咱們能夠看處處理數據時的進步。
const MOVIES_AND_ACTORS_URL = 'http://localhost:3000/moviesAndActors';
function fetchDataV2() {
$.get(MOVIES_AND_ACTORS_URL, movies => renderRoot(movies));
}
function renderRoot(movies) {
movies.forEach(movie => {
$('#movies').append(buildMovieElement(movie));
movie.actors && movie.actors.forEach(actor => {
const selector = '#' + getMovieId(movie) + ' .actors';
const actorElement = buildActorElement(actor);
$(selector).append(actorElement);
})
});
}複製代碼
咱們的新應用會比以前的版本更快,可是這還不夠完美。若是你打開 http://localhost:4000
而且看看咱們簡易的網頁,你會看到像這樣的東西:
若是你看得仔細點,你會發現咱們的頁面使用了電影的標題和圖片,演員的名字和圖片(也就是說,在電影對象中的 8 個字段,咱們只使用了 2 個,在演員對象中有 7 個字段,咱們也只使用了 2 個)。這意味着咱們浪費了咱們所請求的四分之三的信息!過量的帶寬使用不只會影響網頁的表現,也會提升你的設備花銷!
一個精明的後端開發者可能會笑笑而後快速實現一個查詢字段,根據傳進來的字段名稱來動態返回請求所需的字段。
例如,與其使用 curl http://localhost:3000/moviesAndActors
,咱們更傾向於 curl http://localhost:3000/moviesAndActors?fields=title,image
。咱們甚至有另一個查詢參數 actor_fields
用來指定要包含的 actor 模型的成員。例如 curl http://localhost:3000/moviesAndActors?fields=title,image&actor_fields=name,image
。
如今,這在咱們簡易的應用中算是優化的實現,可是同時它也引進了創造自定義路徑給特定客戶端應用的壞習慣。當你開始構建 iOS 應用,而它須要顯示的信息和網頁、Android 應用不一樣時,這種問題會發生得愈來愈多。
若是咱們能夠構建一個普遍的 API 來顯性表示咱們數據模型中的實體和實體間的關係,卻並不須要額外付出 1 + M + M + sum(Am)
的性能損失,那不是很美妙嗎?好消息是,咱們真的能夠!
經過 GraphQL,咱們能夠直接跳過優化查詢來獲取咱們所需的全部信息,無需多餘的操做,只須要直接的查詢:
query MoviesAndActors {
movies {
title
image
actors {
image
name
}
}
}複製代碼
注意!本身試試,打開 GraphiQL(一個基於 GraphQL IDE 神奇的瀏覽器),輸入地址 http://localhost:5000 並運行上面的查詢語句。
如今,讓咱們更深刻地探討一下 GraphQL。
GraphQL 採起和 REST 徹底不一樣的方法來訪問 API。它不依賴於 HTTP 架構中的動做與 URI,而是基於指令式的查詢語言和強力的基於數據的類型系統。類型系統在客戶端和服務端之間提供了強類型的條約,而且查詢語句提供一種機制來讓客戶端的開發者獲取任意所需數據給頁面。
GraphQL 鼓勵你把數據想象成是一個虛擬的信息圖。實體包含了叫作 type 的信息,而且這些 type 能夠和其餘字段關聯。查詢從頂部開始,遍歷虛擬圖的同時獲取所需的信息。。
「虛擬圖」 更傾向於用 schema 描述。schema 是 type、interface、enum 和 union 的集合,用來構建你的 API 數據模型。GraphQL 甚至包含了一種通用的 schema 語言來定義咱們的 API。例如,這是咱們電影 API 的 schema:
schema {
query: Query
}
type Query {
movies: [Movie]
actors: [Actor]
movie(id: Int!): Movie
actor(id: Int!): Actor
searchMovies(term: String): [Movie]
searchActors(term: String): [Actor]
}
type Movie {
id: Int
title: String
image: String
release_year: Int
tags: [String]
rating: Float
actors: [Actor]
}
type Actor {
id: Int
name: String
image: String
dob: String
num_credits: Int
movies: [Movie]
}複製代碼
類型系統爲了打開大門引進大量美妙的東西,包含了更好的工具,更好的文檔,還有效率更高的應用。有許多值得稱道的東西,可是如今咱們先跳過,重點放在用更多的場景來顯示 REST 和 GraphQL 之間的不一樣。
一個 簡單的 google 搜索 顯示了許多人認爲對 REST API 的最佳版本化實踐(或者改革)。咱們不會陷入這個問題,可是我真的想要說明這不是一個簡單的問題。其中一個緣由是版本化很難,由於咱們很難知道什麼樣的應用和裝置要用到什麼樣的信息。
添加信息對於 REST 和 GraphQL 來講都很容易。添加字段對 REST 客戶端來講更麻煩,對 GraphQL 來講則會安全地無視它,直到你改變查詢方式。然而,刪除和修改信息又是另一回事了。
在 REST 中,咱們很難從字段層面上得知哪些信息被用到了。咱們可能知道有一個路徑 /movies
在使用,可是咱們不知道客戶端是否使用字段 title,image 或者都用。其中一個可能的方案就是添加一個查詢參數 fields
來指定返回字段,可是這些參數應該爲可選項。所以,你會發現咱們在路徑層面上做出的改進,引入了新的路徑 /v2/movies
。這有用但同時也增長了咱們 API 的範圍,讓開發者在更新 API 和維護文檔的可讀性上的負擔更重。
然而在 GraphQL 上的版本化則很不一樣。每一個 GraphQL 查詢都須要準確地代表請求所需的字段。事實上這是規定,表明咱們準確地知道在請求什麼信息,咱們能夠所以來反問本身請求有多頻繁和由誰請求。GraphQL 同時包含了原始命令來讓咱們用不支持字段來修飾一個 schema,經過不支持字段和消息來解釋爲何它們不被支持。
GraphQL 上的版本化看起來像這樣:
在 REST 裏緩存很直接也頗有用。事實上,緩存是 六個 RSET 設計約束之一 ,同時暴露在 RESTful 的設計當中。若是路徑 /movies/1
的響應指出響應能夠被緩存,這樣以後來自 /movies/1
的請求均可以以使用緩存來替換。這很簡單。
在 GraphQL 裏緩存的方式有一點點不一樣。在 GraphQL API 裏緩存,每每須要對於每一個 API 中的對象引入一些特別的識別器。當每一個對象均有本身獨有的 id,客戶端就能夠構建規範化的緩存,經過識別器來可靠地給對象緩存、更新並使之失效。當客戶端的查詢指向對象,將會使用在緩存中的對象做爲替換。若是你有興趣瞭解更多有關 GraphQL 裏面緩存的工做原理,點擊 更深刻了解各個部分。
開發者經驗對於應用開發來講是相當重要的,而且是工程師們花費這麼多時間來構建好用的工具的緣由。這裏的比較不免會有一些主觀的東西夾入其中,但我認爲仍是有許多值得一提的東西。
REST 嘗試搭建了一個擁有各類工具的豐富的生態圈,幫助開發者們撰寫文檔,測試並審查 RESTful API,而且它真的作到了。所以有不少的開發者加入,REST API 規模增加。路徑的數量迅速變得龐大起來,不足之處也變得愈來愈明顯,而且版本化愈加困難。
GraphQL 真的勝在開發者經驗這一部分。類型系統爲美妙的工具打開大門,例如 GraphiQL IDE,和內嵌在 schema 的文檔。同時在 GraphiQL 裏對於每一個路徑來講,與其依賴文檔來發現數據是否可用,經過類型安全的語言和自動完成,你能夠快速構建一個 API。同時 GraphQL 是設計用來和現代的前端框架搭配的,例如 React 和 Redux。若是你想要構建 React 應用,我強烈推薦看看 Relay 或者 Apollo client。
GraphQL 提供更獨具一格且異常強力的工具來快速構建一個數據驅動的應用。REST 不會馬上就消失,可是會有大量應用須要 GraphQL ,特別是想要構建客戶端應用的時候。
若是你有興趣瞭解更多,看看 Scaphold.io’s GraphQL 後端即服務。 在 幾分鐘內構建一個部署在 AWS 上,使用 GraphQL API 的產品,而且準備自定義和拓展你的業務邏輯。
希望這篇文章令您有所收穫,若您有任何建議或者意見,歡迎提出!謝謝!
掘金翻譯計劃 是一個翻譯優質互聯網技術文章的社區,文章來源爲 掘金 上的英文分享文章。內容覆蓋 Android、iOS、React、前端、後端、產品、設計 等領域,想要查看更多優質譯文請持續關注 掘金翻譯計劃。