精讀《REST, GraphQL, Webhooks, & gRPC 如何選型》

1 引言

每當項目進入聯調階段,或者提早約定接口時,先後端就會聚在一塊兒熱火朝天的討論起來。可能 99% 的場景都在約定 Http 接口,討論 URL 是什麼,入參是什麼,出參是什麼。前端

有的團隊先後端接口約定更加高效,後端會拿出接口定義代碼,前端會轉換成(或自動轉成)Typescript 定義文件。node

但這些工做都針對於 Http 接口,今天經過 when-to-use-what-rest-graphql-webhooks-grpc 一文,拋開聯調時千遍一概的 Http 接口,一塊兒看看接口還能夠怎麼約定,分別適用於哪些場景,你如今處於哪一個場景。git

2 概述

本文主要講了四種接口設計方案,分別是:REST、gRPC、GraphQL、Webhooks,下面分別介紹一下。github

REST

REST 也許是最通用,也是最經常使用的接口設計方案,它是 無狀態的,以資源爲核心,針對如何操做資源定義了一系列 URL 約定,而操做類型經過 GET POST PUT DELETE 等 HTTP Methods 表示。web

REST 基於原生 HTTP 接口,所以改形成本很小,並且其無狀態的特性,下降了先後端耦合程度,利於快速迭代。json

隨着將來發展,REST 可能更適合提供微服務 API。後端

使用舉例:api

curl -v -X GET https://api.sandbox.paypal.com/v1/activities/activities?start_time=2012-01-01T00:00:01.000Z&end_time=2014-10-01T23:59:59.999Z&page_size=10 \
-H "Content-Type: application/json" \
-H "Authorization: Bearer Access-Token"

gRPC

gRPC 是對 RPC 的一個新嘗試,最大特色是使用 protobufs 語言格式化數據。安全

RPC 主要用來作服務器之間的方法調用,影響其性能最重要因素就是 序列化/反序列化 效率。RPC 的目的是打造一個高效率、低消耗的服務調用方式,所以比較適合 IOT 等對資源、帶寬、性能敏感的場景。而 gRPC 利用 protobufs 進一步提升了序列化速度,下降了數據包大小。性能優化

使用舉例:

gRPC 主要用於服務之間傳輸,這裏拿 Nodejs 舉例:

1.定義接口。因爲 gRPC 使用 protobufs,因此接口定義文件就是 helloword.proto:

// The greeting service definition.
service Greeter {
  // Sends a greeting
  rpc SayHello (HelloRequest) returns (HelloReply) {}
  // Sends another greeting
  rpc SayHelloAgain (HelloRequest) returns (HelloReply) {}
}

// The request message containing the user's name.
message HelloRequest {
  string name = 1;
}

// The response message containing the greetings
message HelloReply {
  string message = 1;
}

這裏定義了服務 Greeter,擁有兩個方法:SayHelloSayHelloAgain,經過 message 關鍵字定義了入參與出參的結構。

事實上利用 protobufs,傳輸數據時僅傳送不多的內容,做爲代價,雙方都要知道接口定義規則才能序列化/反序列化。

2.定義服務器:

function sayHello(call, callback) {
  callback(null, { message: "Hello " + call.request.name });
}

function sayHelloAgain(call, callback) {
  callback(null, { message: "Hello again, " + call.request.name });
}

function main() {
  var server = new grpc.Server();
  server.addProtoService(hello_proto.Greeter.service, {
    sayHello: sayHello,
    sayHelloAgain: sayHelloAgain
  });
  server.bind("0.0.0.0:50051", grpc.ServerCredentials.createInsecure());
  server.start();
}

咱們在 50051 端口支持了 gRPC 服務,並註冊了服務 Greeter,並對 sayHello sayHelloAgain 方法作了一些業務處理,並返回給調用方一些數據。

3.定義客戶端:

function main() {
  var client = new hello_proto.Greeter(
    "localhost:50051",
    grpc.credentials.createInsecure()
  );
  client.sayHello({ name: "you" }, function(err, response) {
    console.log("Greeting:", response.message);
  });
  client.sayHelloAgain({ name: "you" }, function(err, response) {
    console.log("Greeting:", response.message);
  });
}

能夠看到,客戶端和服務端同時須要拿到 proto 結構,客戶端數據發送也要依賴 proto 包提供的方法,框架會內置作掉序列化/反序列化的工做。

也有一些額外手段將 gRPC 轉換爲 http 服務,讓網頁端也享受到其高效、低耗的好處。可是不要忘了,RPC 最經常使用的場景是 IOT 等硬件領域,網頁場景也許不會在意節省幾 KB 的流量。

GraphQL

GraphQL 不是 REST 的替代品,而是另外一種交互形式:前端決定後端的返回結果。

GraphQL 帶來的最大好處是精簡請求響應內容,不會出現冗餘字段,前端能夠決定後端返回什麼數據。但要注意的是,前端的決定權取決於後端支持什麼數據,所以 GraphQL 更像是精簡了返回值的 REST,然後端接口也能夠一次性定義完全部功能,而不須要逐個開發。

再次強調,相比 REST 和 gRPC,GraphQL 是由前端決定返回結果的反模式。

使用舉例:

原文推薦參考 GitHub GraphQL API

好比查詢某個組織下的成員,REST 風格接口多是:

curl -v https://api.github.com/orgs/:org/members

含義很明確,但問題是返回結果不明確,必須實際調試才知道。換成等價的 GraphQL 是這樣的

query {
  organization(login: "github") {
    members(first: 100) {
      edges {
        node {
          name
          avatarUrl
        }
      }
    }
  }
}

返回的結果和約定的格式結構一致,且不會有多餘的字段:

{
  "data": {
    "organization": {
      "members": {
        "edges": [
          {
            "node": {
              "name": "Chris Wanstrath",
              "avatarUrl": "https://avatars0.githubusercontent.com/u/2?v=4"
            }
          },
          {
            "node": {
              "name": "Justin Palmer",
              "avatarUrl": "https://avatars3.githubusercontent.com/u/25?v=4"
            }
          }
        ]
      }
    }
  }
}

可是能看出來,這樣作須要一個系統幫助你寫 query,不少框架都提供這個功能,好比 apollo-client

Webhooks

若是說 GraphQL 顛覆了先後端交互模式,那 Webhooks 能夠說是徹頭徹尾的反模式了,由於其定義就是,前端不主動發送請求,徹底由後端推送。

它最適合解決輪詢問題。或者說輪詢就是一種妥協的行爲,當後端不支持 Webhooks 模式時。

使用舉例:

Webhooks 自己也能夠由 REST 或者 gRPC 實現,因此就不貼代碼了。舉個經常使用例子,好比你的好友發了一條朋友圈,後端將這條消息推送給全部其餘好友的客戶端,就是 Webhooks 的典型場景。


最後做者給出的結論是,這四個場景各有不一樣使用場景,沒法相互替代:

  • REST:無狀態的數據傳輸結構,適用於通用、快速迭代和標準化語義的場景。
  • gRPC:輕量的傳輸方式,特殊適合對性能高要求或者環境苛刻的場景,好比 IOT。
  • GraphQL: 請求者能夠自定義返回格式,某些程度上能夠減小先後端聯調成本。
  • Webhooks: 推送服務,主要用於服務器主動更新客戶端資源的場景。

3 精讀

REST 並不是適用全部場景

本文給了咱們一個更大的視角看待平常開發中的接口問題,對於奮戰在一線的前端同窗,接觸到 90% 的接口都是非 REST 規則的 Http 接口,能真正落實 REST 的團隊其實很是少。這其實暴露了一個重要問題,就是 REST 所帶來的好處,在整套業務流程中到底佔多大的比重?

不只接口設計方案的使用要分場景,針對某個接口方案的重要性也要再繼續細分:在作一個開放接口的項目,提供 Http 接口給第三方使用,這時必須好好規劃接口的語義,因此更容易讓你們達成一導致用 REST 約定;而開發一個產品時,其實先後端不關心接口格式是否規範,甚至在開發內網產品時,性能和冗餘都不會考慮,效率放在了第一位。因此第一點啓示是,不要埋冤當前團隊業務爲何沒有使用某個更好的接口約定,由於接口約定極可能是業務形態決定的,而不是憑空作技術對比從而決定的。

gRPC 是服務端交互的首選

前端同窗轉 node 開發時,很喜歡用 Http 方式進行服務器間通信,但可能會疑惑,爲何公司內部 Java 或者 C++ 寫的服務都不提供 Http 方式調用,而是另一個名字。瞭解 gRPC 後,能夠認識到這些平臺都是對 RPC 方式的封裝,服務器間通訊對性能和延時要求很是高,因此比較適合專門爲性能優化的 gRPC 等服務。

GraphQL 須要配套

GraphQL 不是 REST 的替代品,因此不要想着團隊從 Http 接口遷移到 GraphQL 就能提高 X% 的開發效率。GraphQL 方案是一種新的先後端交互約定,因此上手成本會比較高,同時,爲了方便前端同窗拼 query,等於把一部分後端工做量轉移給了前端,若是此時沒有一個足夠好用的平臺快速查閱、生成、維護這些定義,開發效率可能不升反降。

總的來講,對外開放 API 或者擁有完整配套的場景,使用 GraphQL 是比較理想的,但對於快速迭代,平臺又不夠成熟的團隊,繼續使用標準 Http 接口能夠更快完成項目。

Webhooks 解決特殊場景問題

對於第三方平臺驗權、登錄等 沒有前端界面作中轉的場景,或者強安全要求的支付場景等,適合用 Webhooks 作數據主動推送。說白了就是在前端無從參與,或者由於前端安全問題不適合參與時,就是 Webhooks 的場景。很顯然 Webhooks 也不是 Http 的替代品,不過的確是一種新的先後端交互方式。

對於慢查詢等場景,前端廣泛使用輪詢完成,這和 Socket 相比體驗更弱,但無狀態的特性反而會下降服務器負擔,因此慢查詢和即時通信要區分對待,用戶對消息及時性的敏感程度決定了使用哪一種方案。

4 總結

最後,上面總結的內容必定還有許多疏漏,歡迎補充。

5 更多討論

討論地址是: 精讀《REST, GraphQL, Webhooks, & gRPC 如何選型》 · Issue #102 · dt-fe/weekly

若是你想參與討論,請點擊這裏,每週都有新的主題,週末或週一發佈。

相關文章
相關標籤/搜索