爲何說GraphQL能夠取代REST API?

爲何說GraphQL能夠取代REST API?

爲何說GraphQL能夠取代REST API?
做者|Azat Mardan
譯者|薛命燈
在這篇文章中,我將介紹 GraphQL 的優點,以及爲何它會變得如此受歡迎。
幾年前,我在 DocuSign 帶領了一個開發團隊,任務是重寫一個有數千萬個用戶在使用的 Web 應用程序。當時尚未能夠支持前端的 API,由於從一開始,Web 應用程序就是一個.NET 大單體。西雅圖的 API 團隊在將拆分單體,並逐步暴露出 RESTful API。這個 API 團隊由兩名工程師組成,發佈週期爲一個月,而咱們在舊金山的前端團隊每週都會發布新版本。
API 團隊的發佈週期太長,由於不少(幾乎全部)功能都必須進行手動測試,這是能夠理解的。它畢竟是一個單體,並且沒有適當的自動化測試——若是他們修改了一個地方,不知道在應用程序的其餘地方會出現什麼問題。
我記得有一次,咱們的前端團隊面臨爲某大會交付新版本的壓力,但咱們忘記跟進一個重要的 API 變動,這個變動未被包含在即將發佈的 API 版本中。咱們要麼一直等待,直到錯過截止日期,要麼有人願意放棄優先權,以便讓咱們的變動包括在即將發佈的版本中。所幸的是,這個變動最後被包含在新版本中,咱們也及時發佈了新的前端版本。我真的但願當時咱們已經使用了 GraphQL,由於它能夠消除對外部團隊及其發佈週期的重度依賴。
不少公司已經在內部從 RESTful 轉向了 GraphQL API:IBM、Twitter、Walmart Labs、紐約時報、Intuit、Coursera,等等。
其餘一些公司不只是在內部並且還將外部 API 也轉爲 GraphQL:AWS、Yelp、GitHub、Facebook 和 Shopify,等等。GitHub 甚至打算中止使用 REST API,他們的 v4 版本只使用 GraphQL。
GraphQL 到底是一個炒做流行語仍是真正會帶來一場變革?有趣的是,我以前列出的大多數從 GraphQL 獲益的公司都有如下這些共同點。前端

  • 他們擁有包括移動端在內的多個客戶端;
  • 他們正在轉向或者已經採用了微服務架構;
  • 他們的遺留 REST API 數量暴增,變得十分複雜;
  • 他們但願消除客戶端團隊對 API 團隊的依賴;
  • 他們注重良好的 API 文檔和開發者體驗。
    GitHub 工程團隊代表了他們的動機:
    「GraphQL 彌合了發佈的內容與可使用的內容之間的差距。咱們真的很期待可以同時發佈它們。GraphQL 表明了 API 開發的巨大飛躍。類型安全、內省、生成文檔和可預測的響應都爲咱們平臺的維護者和消費者帶來了好處。咱們期待着由 GraphQL 提供支持的平臺進入新時代,也但願大家也這樣作!」
    GraphQL 加速了開發速度,提高了開發者體驗,並提供了更好的工具。我並非說這絕對是這樣的,但我會盡力說明 GraphQL 與 REST 之間的爭論點及其緣由。

    超級數據聚合器

    我是 Indeed(世界排名第一的求職網站)的軟件工程負責人,因此讓咱們先來看看 Indeed.com 的主頁和職位查詢結果頁面。它們分別發出了 10 和 11 個 XHR 請求。
    爲何說GraphQL能夠取代REST API?
    須要注意的是,在 REST 中使用 POST 進行頁面瀏覽並非很「正規」。
    爲何說GraphQL能夠取代REST API?
    如下是其中的一些調用:java

  • GET:
    https://inbox.indeed.com/api/getConversationCount
  • GET :
    https://www.indeed.com/rpc/jobdescs
  • GET :
    https://www.indeed.com/rpc/vjslog
  • GET :
    https://www.indeed.com/rpc/preccount
  • POST :
    https://www.indeed.com/rpc/jobalert
  • POST :
    https://www.indeed.com/rpc/count
    在使用 GraphQL 時,上面的這些請求能夠被包含在單個查詢和單個請求中。
query HomePage {
 getConversationCount(...) {
    ...
 }
 jobdescs(...) {
    ...
 }
 vjslog(...) {
    ...
 }
 preccount(...) {
    …
 }
 jobalert(...) {
    …
 }
 count(...) {
    …
 }
}

響應結果多是這樣的:python

{
 "data": {
   "getConversationCount": [
     {
       ...
     }
   ],
   "vjslog": [...],
   "preccount": [...],
     "jobalert": [...],
   "count": {}
 },
 "errors": []
}

一般,單個調用比多個調用更方便、更有效,由於它須要更少的代碼和更少的網絡開銷。來自 PayPal 過程團隊的開發體驗還證明,不少 UI 工做實際上不是 UI 工做,而是其餘任務,例如前端和後端之間的通訊:
「咱們發現,UI 開發人員實際用於構建 UI 的時間不到三分之一,剩下的時間用於肯定在何處以及如何獲取數據、過濾 / 映射數據以及編排 API 調用,還有一些用於構建和部署。」
須要注意的是,有實時使多個請求也是有必要的,例如多個單獨的請求能夠快速且異步獨立地獲取不一樣的數據,若是採用了微服務架構,它們會增長部署靈活性,並且它們的故障點是多個,而不是一個。
此外,若是頁面是由多個團隊開發的,GraphQL 提供了一個功能,能夠將查詢分解稱爲片斷。稍後咱們將詳細介紹這方面的內容。
從更大的角度來看,GraphQL API 的主要應用場景是 API 網關,在客戶端和服務之間提供了一個抽象層。
爲何說GraphQL能夠取代REST API?
微服務架構很好,但也存在一些問題,GraphQL 能夠用來解決這些問題。如下是來自 IBM 在微服務架構中使用 GraphQL 的經驗:
「總的來講,GraphQL 微服務的開發和部署都很是快。他們 5 月份開始開發,7 月份就進入了生產環境。由於他們不須要徵得許可,直接開幹。他強烈推薦這個方案,比開會討論好太多了。」
接下來,讓咱們逐一討論 GraphQL 的每個好處。ios

提升開發速度

首先,GraphQL 有助於減小發出的請求數。經過單個調用來獲取所需的數據比使用多個請求要容易得多。從工程師的角度來看,這加快了開發速度。後面我會解釋更多有關爲何會提高開發速度的緣由,但如今我想先說明另外一個問題。
後端和客戶端團隊須要經過密切合做來定義 API、測試它們,並作出更改。前端、移動、物聯網(例如 Alexa)等客戶端團隊不斷迭代功能,並嘗試使用新的 UX 和設計。他們的數據需求常常發生變化,後端團隊必須跟上他們的節奏。若是客戶端和後端代碼由同一團隊負責,那麼問題就沒那麼嚴重了。Indeed 的大多數工程團隊都是由全棧工程師組成,但並不是所有都是這樣。對於非全棧團隊,客戶端團隊常常由於依賴了後端團隊開發速度受到影響。
當我轉到 Job Seeker API 團隊時,移動團隊開始咱們的開發進度。咱們之間有不少關於參數、響應字段和測試的事情須要溝通。
在使用了 GraphQL 以後,客戶端工程師就能夠徹底控制前端,不須要依賴任何人,由於他們能夠告訴後端他們須要什麼以及響應結構應該是怎樣的。他們使用了 GraphQL 查詢,它們會告訴後端 API 應該要提供哪些數據。
客戶端工程師不須要花時間讓後端 API 團隊添加或修改某些內容。GraphQL 具備自文檔的特色,因此能夠節省一些用於查找文檔以便了解如何使用 API 的時間。我相信大多數人曾經在找出確切的請求參數方面浪費了不少時間。GraphQL 協議自己及其社區在文檔方面爲咱們提供了一些有用的工具。在某些狀況下,能夠從模式自動生成文檔。其餘時候,只需使用 GraphiQL Web 界面就足以編寫一個查詢。
來自紐約時報的工程師表示,他們在轉到 GraphQL 和 Relay 以後,在作出變動時不須要改太多的東西:
「當咱們想要更新全部產品的設計時,再也不須要修改多個代碼庫。這就是咱們想要的。咱們認爲 Relay 和 GraphQL 是幫助咱們實現這個偉大目標的完美工具。」
當一家公司已經擁有大量 GraphQL API,而後有人想出了一個新的產品創意,這也是我最喜歡 GraphQL 的應用場景。使用已有的 GraphQL API 實現原型比調用各類 REST 端點(將提供太少或太多的數據)或爲新應用程序構建新的 REST API 要快得多。
開發速度的提高與開發者體驗的提高密切相關。git

提高開發者體驗

GraphQL 提供了更好的開發者體驗(DX),開發者將花更少的時間思考如何獲取數據。在使用 Apollo 時,他們只須要在 UI 中聲明數據。數據和 UI 放在一塊兒,閱讀代碼和編寫代碼都變得更方便。
一般,在開發 UI 時須要在 UI 模板、客戶端代碼和 UI 樣式之間跳轉。GraphQL 容許工程師在客戶端開發 UI,減小摩擦,由於工程師在添加或修改代碼時無需在文件之間切換。若是你熟悉 React,這裏有一個很好的比喻:GraphQL 之於數據,就像 React 之於 UI。
下面是一個簡單的示例,UI 中直接包含了屬性名稱 launch.name 和 launch.rocket.name。github

const GET_LAUNCHES = gql`
 query launchList($after: String) {
   launches(after: $after) {
     launches {
       id
       name
       isBooked
       rocket {
         id
         name
       }
     }
   }
 }
`;

export default function Launches() {
 return (
   <Query query={GET_LAUNCHES}>
     {({ data, loading, error }) => {
       if (loading) return <Loading />;
       if (error) return <p>ERROR</p>;

       return (
           <div>
           {data.launches.launches.map(launch => (
               <div
                 key={launch.id}
               >{launch.name}<br/>
               Rocket: {launch.rocket.name}
               </div>
             ))}
         </div>
       );
     }}
   </Query>
 );
};

使用這種方法,能夠很是容易地修改或向 UI 或查詢(gql)添加新字段。React 組件的可移植性更強了,由於它們描述了所需的全部數據。
如前所述, GraphQL 提供了更好的文檔,並且還有一個叫做 GraphiQL 的 IDE:
爲何說GraphQL能夠取代REST API?
前端工程師很喜歡 GraphiQL,下面引用 Indeed 的一位高級工程師說過的話:
「我認爲開發體驗中最好的部分是可以使用 GraphiQL。對我來講,與典型的 API 文檔相比,這是一種編寫查詢更有效的輔助方法」。
GraphQL 的另外一個很棒的功能是片斷,由於它容許咱們在更高的組件層面重用查詢。
這些功能改善了開發者體驗,讓開發人員更快樂,更不容易出現 JavaScript 疲勞。web

提高性能

工程師並非惟一從 GraphQL 中受益的人。用戶也會從中受益,由於應用程序的性能得到了提高(能夠感知到的):
1.減小了有效載荷(客戶端只須要必要的東西);
2.多個請求合併爲一個請求可減小網絡開銷;
3.使用工具能夠更輕鬆地實現客戶端緩存和後端批處理和後端緩存;
4.預取;
5.更快的 UI 更新。
PayPal 使用 GraphQL 從新設計了他們的結帳流程。下面是來自用戶的反饋:
「REST 的原則並無爲 Web 和移動應用及其用戶的需求考慮,這個在結帳優化交易中體現得尤其明顯。用戶但願可以儘快完成結帳,若是應用程序使用了不少原子 REST API,就須要在客戶端和服務器之間進行屢次往返以獲取數據。咱們的結帳每次往返網絡時間至少須要 700 毫秒,這還不包括服務器處理請求的時間。每次往返都會致使渲染變慢,用戶體驗很差,結算轉換率也會下降。」
性能改進中有一項是「多個請求組合成一個請求能夠減小網絡開銷」。對於 HTTP/1 而言,這是很是正確的,由於它沒有 HTTP/2 那樣的多路複用機制。但儘管 HTTP/2 提供的多路複用機制有助於優化單獨的請求,但它對於圖遍歷(獲取相關或嵌套對象)並無實際幫助。讓咱們來看一看 REST 和 GraphQL 是如何處理嵌套對象和其餘複雜請求的。npm

標準化和簡化複雜的 API

一般,客戶端會發出複雜的請求來獲取有序、排好序、被過濾過的數據或子集(用於分頁),或者請求嵌套對象。GraphQL 支持嵌套數據和其餘難以使用標準 REST API 資源(也叫端點或路由)實現的查詢。
例如,咱們假設有三種資源:用戶、訂閱和簡歷。工程師須要按順序進行兩次單獨的調用(這會下降性能)來獲取一個用戶簡歷,首先須要經過調用獲取用戶資源,拿到簡歷 ID,而後再使用簡歷 ID 來獲取簡歷數據。對於訂閱來講也是同樣的。
1.GET /users/123:響應中包含了簡歷 ID 和工做崗位通知訂閱的 ID 清單;
2.GET /resumes/ABC:響應中包含了簡歷文本——依賴第一個請求;
3.GET /subscriptions/XYZ:響應中包含了工做崗位通知的內容和地址——依賴第一個請求。
上面的示例很糟糕,緣由有不少:客戶端可能會得到太多數據,而且必須等待相關的請求完成了之後才能繼續。此外,客戶端須要實現如何獲取子資源(例如創建或訂閱)和過濾。
想象一下,一個客戶端可能只須要第一個訂閱的內容和地址以及簡歷中的當前職位,另外一個客戶端可能須要全部訂閱和整個簡歷列表。因此,若是使用 REST API,對第一個客戶端來講有點不划算。
另外一個例子:用戶表裏可能會有用戶的名字和姓氏、電子郵件、簡歷、地址、電話、社會保障號、密碼(固然是通過混淆的)和其餘私人信息。並不是每一個客戶端都須要全部字段,有些應用程序可能只須要用戶電子郵件,因此向這些應用程序發送社會保障號等信息就不太安全。
固然,爲每一個客戶端建立不一樣的端點也是不可行的,例如 /api/v1/users 和 /api/v1/usersMobile。
事實上,各類客戶端一般都有不一樣的數據需求:/api/v1/userPublic、/api/v1/userByName、/api/v1/usersForAdmin,若是這樣的話,端點會呈指數級增加。
GraphQL 容許客戶要求 API 發送他們想要的字段,這將使後端工做變得更加容易:/api/gql——全部客戶端只須要這個端點。
注意:對於 REST 和 GraphQL,後端都須要使用訪問控制級別。
或者可使用舊 REST 來實現 GraphQL 的不少功能。可是這樣要付出什麼代價?後端能夠支持複雜的 RESTful 請求,這樣客戶端就可使用字段和嵌套對象進行調用:json

GET /users/?fields=name,address&include=resumes,subscriptions

上面的請求將比使用多個 REST 請求更好,但它不是標準化的,不受客戶端庫支持,並且這樣的代碼也更難編寫和維護。對於相對複雜的 API,工程師須要在查詢中使用本身的查詢字符串參數約定,最終獲得相似 GraphQL 的東西。既然 GraphQL 已經提供了標準和庫,爲何還要基於 REST 設計本身的查詢約定呢?
將複雜的 REST 端點與如下的 GraphQL 嵌套查詢進行對比,嵌套查詢使用了更多的過濾條件,例如「只要給我前 X 個對象」和「按時間按升序排列」(能夠添加無限制的過濾選項):axios

{
   user (id: 123) {
       id
       firstName
       lastName
       address {
           city
           country
           zip
       }
       resumes (first: 1, orderBy: time_ASC) {
           text
           title
           blob
           time
         }
         subscriptions(first: 10) {
           what
           where
           time
       }
   }
}
}

在使用 GraphQL 時,咱們能夠在查詢中保留嵌套對象,對於每一個對象,咱們將精確地得到咱們須要的數據,很少也很多。
響應消息的數據格式反映了請求查詢的結構,以下所示:

{
   "data": {
       "user": {
           "id": 123,
           "firstName": "Azat",
           "lastName": "Mardan",
           "address": {
               "city": "San Francisco",
               "country": "US",
               "zip": "94105"
           },
           "resumes" [
                 {
                   "text": "some text here...",
                   "title": "My Resume",
                   "blob": "<BLOB>",
                   "time": "2018-11-13T21:23:16.000Z"
                 },
           ],
             "subscriptions": [ ]
       },
   "errors": []    
}

相比複雜的 REST 端點,使用 GraphQL 的另外一個好處是提升了安全性。這是由於 URL 常常會被記錄下來,而 RESTful GET 端點依賴於查詢字符串(是 URL 的一部分)。這可能會暴露敏感數據,因此 RESTful GET 請求的安全性低於 GraphQL 的 POST 請求。我打賭這就是爲何 Indeed 主頁會使用 POST 發出「閱讀」頁面請求。
使用 GraphQL 可有更容易地實現分頁等關鍵功能,這要歸功於查詢以及 BaaS 提供商提供的標準,以及後端的實現和客戶端庫使用的標準。

改進的安全性、強類型和驗證

GraphQL 的 schema 與語言無關。對前面的示例進行擴展,咱們能夠在 schema 中定義 Address 類型:

type Address {
   city: String!
   country: String!
   zip: Int
}

String 和 Int 是標量類型,! 表示字段不可爲空。
schema 驗證是 GraphQL 規範的一部分,所以像這樣的查詢將返回錯誤,由於 name 和 phone 不是 Address 對象的字段:

{
   user (id: 123) {
       address {
           name
           phone
       }
   }
}

咱們可使用咱們的類型構建複雜的 GraphQL schema。例如,用戶類型可能會使用咱們的地址、簡歷和訂閱類型,以下所示:

type User {
   id: ID!
   firstName: String!
   lastName: String!
   address: Address!
   resumes: [Resume] 
   subscriptions: [Subscription]
}

Indeed 的大量對象和類型都是使用 ProtoBuf 定義的。類型化數據並非什麼新鮮事物,並且類型數據的好處也是衆所周知。與發明新的 JSON 類型標準相比,GraphQL 的優勢在於已經存在能夠從 ProtoBuf 自動換換到 GraphQL 的庫。即便其中一個庫(rejoiner:https://github.com/google/rejoiner) 不能用,也能夠開發本身的轉換器。
GraphQL 提供了比 JSON RESTful API 更強的安全性,主要有兩個緣由:強類型 schema(例如數據驗證和無 SQL 注入)以及精肯定義客戶端所需數據的能力(不會無心泄漏數據)。
靜態驗證是另外一個優點,能夠幫助工程師節省時間,並在進行重構時提高工程師的信心。諸如 eslint-plugin-graphql(https://github.com/apollographql/eslint-plugin-graphql)之類的工具可讓工程師知道後端發生的變化,並讓後端工程師確保不會破壞客戶端代碼
保持前端和後端之間的契約是很是重要的。在使用 REST API 時,咱們要當心不要破壞了客戶端代碼,由於客戶端沒法控制響應消息。相反,GraphQL 爲客戶端提供了控制,GraphQL 能夠頻繁更新,而不會由於引入了新類型形成重大變動。由於使用了 schema,因此 GraphQL 是一種無版本的 API。

GraphQL 的實現

在選擇實現 GraphQL API 的平臺時,Node 是一個候選項,由於最初 GraphQL 用於 Web 應用程序和前端,而 Node 是開發 Web 應用程序的首選,由於它是基於 JavaScript 的。使用 Node 能夠很是容易地實現 GraphQL(假設提供了 schema)。事實上,使用 Express 或 Koa 來實現只須要幾行代碼:

const Koa = require('koa');
const Router = require('koa-router'); // koa-router@7.x
const graphqlHTTP = require('koa-graphql');

const app = new Koa();
const router = new Router();

router.all('/graphql', graphqlHTTP({
 schema: schema,
 graphiql: true
}));

app.use(router.routes()).use(router.allowedMethods());

schema 是使用 npm 的 graphql 中的類型來定義的。Query 和 Mutation 是特殊的 schema 類型。
GraphQL API 的大部分實現都在於 schema 和解析器。解析器能夠包含任意代碼,但最多見的是如下五個主要類別:

import com.coxautodev.graphql.tools.SchemaParser;
import javax.servlet.annotation.WebServlet;
import graphql.servlet.SimpleGraphQLServlet;

@WebServlet(urlPatterns = "/graphql")
public class GraphQLEndpoint extends SimpleGraphQLServlet {

   public GraphQLEndpoint() {
       super(SchemaParser.newParser()
               .file("schema.graphqls") //parse the schema file created earlier
               .build()
               .makeExecutableSchema());
   }
}

GraphQL 的 schema 使用 POJO 來定義。GraphQL 端點類使用了 LinkRepository POJO。解析器包含了操做的(例如獲取連接)實際代碼:

@WebServlet(urlPatterns = "/graphql")
public class GraphQLEndpoint extends SimpleGraphQLServlet {

   public GraphQLEndpoint() {
       super(buildSchema());
   }

   private static GraphQLSchema buildSchema() {
       LinkRepository linkRepository = new LinkRepository();
       return SchemaParser.newParser()
               .file("schema.graphqls")
               .resolvers(new Query(linkRepository))
               .build()
               .makeExecutableSchema();
   }
}

在不少狀況下,GraphQL 的 schema 能夠從其餘類型的 schema 自動生成,例如 gRPC、Boxcar、ProtoBuf 或 ORM/ODM。
GraphQL 不必定須要客戶端。一個簡單的 GraphQL 請求就是一個常規的 POST HTTP 請求,其中包含了查詢內容。咱們可使用任意的 HTTP 代理庫(如 CURL、axios、fetch、superagent 等)來生成請求。例如,在終端中使用 curl 發送請求:

curl \
 -X POST \
 -H "Content-Type: application/json" \
 --data '{ "query": "{ posts { title } }" }' \
 https://1jzxrj179.lp.gql.zone/graphql

如下代碼能夠在任意一個現代瀏覽器(爲了不 CORS,請訪問 launchpad.graphql.com)中運行。

fetch('https://1jzxrj179.lp.gql.zone/graphql', {
 method: 'POST',
 headers: { 'Content-Type': 'application/json' },
 body: JSON.stringify({ query: '{ posts { title } }' }),
})
 .then(res => res.json())
 .then(res => console.log(res.data));

雖然構建 GraphQL 請求很容易,可是還須要實現不少其餘東西,好比緩存,由於緩存能夠極大地改善用戶體驗。構建客戶端緩存不是那麼容易,所幸的是,Apollo 和 Relay Modern 等提供了開箱即用的客戶端緩存。

何時不應使用 GraphQL?

固然,完美的解決方案是不存在的(儘管 GraphQL 接近完美),還有一些問題須要注意,例如:
1.它有單點故障嗎?
2.它能夠擴展嗎?
3.誰在使用 GraphQL?
最後,如下列出了咱們本身的有關 GraphQL 可能不是一個好選擇的主要緣由:

  • 當客戶端的需求很簡單時:若是你的 API 很簡單,例如 /users/resumes/123,那麼 GraphQL 就顯得有點重了;
  • 爲了加快加載速度使用了異步資源加載;
  • 在開發新產品時使用新的 API,而不是基於已有的 API;
  • 不打算向公衆公開 API;
  • 不須要更改 UI 和其餘客戶端;
  • 產品開發不活躍;
  • 使用了其餘一些 JSON schema 或序列化格式。

    總 結

    GraphQL 是一種協議和一種查詢語言。GraphQL API 能夠直接訪問數據存儲,但在大多數狀況下,GraphQL API 是一個數據聚合器和一個抽象層,一個能夠提高開發速度、減小維護工做並讓開發人員更快樂的層。所以,GraphQL 比公共 API 更有意義。不少公司開始採用 GraphQL。IBM、PayPal 和 GitHub 聲稱在使用 GraphQL 方面取得了巨大的成功。若是 GraphQL 頗有前途,咱們如今是否能夠中止構建過期且笨重的 REST API,並擁抱 GraphQL?
    英文原文:https://webapplog.com/graphql/

相關文章
相關標籤/搜索