最近一段時間關于GraphQL的讨論很多,一些項目中也相繼用到了這種風格,但使用是否合理,是否存在殺雞用牛刀這樣的問題,還有待商榷。
譯自:Comparing API Architectural Styles: SOAP vs REST vs GraphQL vs RPC
兩個不同的應用需要一個中間程式才能互通,開發者通常會使用應用程式接口(API)進行搭橋,使一個系統能夠通路另一個系統的資訊或功能。
為了在擴容時快速內建應用,實際的API會使用協定或規範來定義消息傳遞的語義和文法。這些規範構成了API架構。
過去幾年曾出現了幾種不同的API架構風格,每種風格都有其特定的标準資料互動模式。而對API架構的選擇引起了無休止的讨論。

現在,很多API使用者放棄REST,并擁抱GraphQL。而在十年之前,對于REST來說則是相反的情況,在于SOAP的競争中,REST大獲全勝。這種觀念的問題在于用于單方面去選擇一個技術,而沒有考慮實際價值以及以與特定場景的比對度。
本文将會按照API風格出現的順序對它們進行讨論,對比各自的優劣勢,并給出各自适合的場景。
RPC是一個規範,它允許在一個不同的上下文中遠端執行功能。RPC将本地程式調用擴充到了HTTP API的上下文中(RPC的最上層大部分都是HTTP)。
一開始的XML-RPC問題比較多,它很難保證XML載體的資料類型。後來出現了一個基于JSON-RPC的RPC API,由于JSON的規範更加具體,是以被認為是SOAP的替代品。 gRPC是一個谷歌在2015年開發的全新RPC版本,插件化支援負載均衡、跟蹤、健康檢查以及身份認證等,gRPC非常适用于微服務間的通信。
用戶端喚醒遠端程式,序列化參數,并在消息中添加額外的資訊,然後将消息發送給服務端。在接收到用戶端的消息後,服務端會反序列化消息中的内容,執行請求的操作,并将結果傳回給用戶端。服務端存根(stub)和用戶端存根(stub)負責參數的序列化和反序列化。
直接簡單的互動方式:RPC使用GET擷取資訊,并使用POST處理其他功能。服務端和用戶端的互動歸結為對後端的調用,并擷取響應結果。
友善添加功能:如果我們對API有新的需求,可以通過簡單地添加新的後端來滿足該需求:1)編寫一個新的功能,然後釋出;2)然後用戶端就可以通過這個後端來滿足需求。
高性能:輕量載體提升了網絡傳輸的性能,這對于共享伺服器以及在網絡上進行并行計算的工作站來說非常重要。RCP可以優化網絡層,使其可以每天在不同的服務間發送大量消息。
與底層系統的強耦合:API的抽象程度與其可複用性相關。與底層系統的耦合越高,API的可複用性就越低。RPC與底層系統的強耦合使其無法在系統和外部API之間進行抽象,同時也增加了安全風險,很容易在API中洩露底層系統的實作細節。RPC的強耦合使其很難實作需求擴充和團隊解耦,客戶要麼會擔心調用特定後端可能帶來的副作用(如安全問題),要麼會因為無法了解服務端的功能命名規則而不知道調用哪個後端。
這裡說的"與底層系統"的耦合,并不是說與核心等底層實作之間的耦合,而是與底層服務的耦合,如與日志服務,鑒權服務等耦合。
可發現性低:RPC無法對API進行自省或無法通過發送的RPC請求來了解其調用的功能。
應該是RPC并沒有像REST API那樣相對嚴格的調用規範,是以有些調用會比較難以了解
功能爆炸:由于很容易添加新的功能,是以相比編輯現有的功能,新增的功能可能會導緻大量功能重疊,也很難去了解。
RPC模式始于80年代,但它一直沒有過時。像Google,Facebook (Apache Thrift)和Twitch(Twirp) 這樣的大型公司利用RPC的高性能特性來獲得高性能、低開銷的消息處理能力(規模龐大的微服務使用短消息進行通信,需要保證通信的暢通)。
指令式API:RPC非常适合向遠端系統發送指令。例如,Slack API就是重指令的接口:加入頻道、離開頻道、發送消息等。是以Slack API的設計者可以使用RPC風格的模型,使功能更簡單、緊湊,也更友善使用。
用于内部微服務客戶API:在整合單個供應商和使用者時,我們不希望(像REST API那樣)花費大量時間來傳輸中繼資料。憑借高消息速率和消息性能,gRPC和Twirp是微服務使用RPC的典範。gRPC背後使用的是HTTP 2,是以能夠優化網絡層,每天可以在不同的服務間傳送大量消息。但如果不關心高性能網絡,轉而期望團隊間能夠使用穩定的API來釋出不同的微服務,那麼可以選擇使用REST。
SOAP是一種XML格式的,高度标準化的web通信協定。在XML-RPC面世一年之後,Microsoft釋出了SOAP,SOAP繼承了XML-RPC的很多特性。而後出現了REST,二者并駕齊驅,但很快REST就後來居上。
XML資料格式多種多樣,加上大量消息結構,使得SOAP稱為一種最冗長的API樣式。
一個SOAP消息包含:
每個消息的開始和結束都要包含一個信封标簽
包含請求或響應的消息體
标頭(如果消息必須确定某些具體要求或額外要求)
請求過程中的錯誤資訊
SOAP API的邏輯是用Web服務描述語言(WSDL)編寫的,該API描述語言定義了後端并描述了可執行的流程。它允許使用不同的程式設計語言和IDEs快速配置通信。
SOAP同時支援有狀态和無狀态消息。在有狀态場景中,服務端會儲存接收到的資訊,該過程可能比較繁重,但對于涉及多方和複雜交易的操作來說是合理的。
語言和平台無關:支援建立基于Web的服務内置功能使SOAP能夠處理獨立于語言和平台的通信,并作出響應。
适用于各種傳輸協定:SOAP支援大量傳輸協定,可以用于多種場景。
内置錯誤處理:SOAP API規範可以傳回Retry XML消息(攜帶錯誤碼和錯誤解釋)
大量安全擴充:內建了WS-Security,SOAP符合企業級事務品質。它為事務提供了隐私和完整性,并可以在消息層面進行加密
如今,由于多種原因,很多開發人員對必須內建SOAP API的想法感到不安。
僅支援XML:SOAP消息包含大量中繼資料,且請求和響應僅支援使用冗長的XML結構。
厚重:由于XML檔案的大小,SOAP服務需要比較大的帶寬。
狹窄的專業知識:建構SOAP API需要深刻了解各種協定,以及嚴格的協定規則。
乏味的消息更新:在添加和移除消息屬性時需要額外的工作量,這導緻SOAP的采用率下降。
目前,SOAP架構大部分用于内部內建企業或其他可信任的夥伴。
高度安全的資料傳輸:SOAP的剛性結構、安全和授權能力使其特别适用于在遵守API提供者和API使用者之間的契約的同時,在API和用戶端之間履行正式的軟體契約。這也是為什麼金融機構和其他企業使用者選擇SOAP的原因。
REST是一個自解釋的、由一組架構限制定義的API架構風格,并被很多API使用者廣泛采用。
作為當今最通用的API風格,它最初出現在2000年的Roy Fielding 的博士論文中。REST使用簡單格式(通常是JSON和XML)來表達服務側的資料。
REST沒有像SOAP那樣嚴格。RESTful架構應該遵循以下六個架構限制:
統一接口:為一個給定的服務(無論是裝置還是應用類型)提供統一的接口。
無狀态:處理請求本身所包含的請求狀态,而伺服器不會存儲與會話相關的任何内容
緩存
用戶端-服務端架構:允許兩端獨立演進
應用系統分層
服務端可以給用戶端提供可執行的代碼
實際上,某些服務僅在一定程度上是RESTful的,而核心使用了RPC風格,将大型服務分割成多個資源,并有效地利用HTTP基礎設施。但關鍵部分使用的是超媒體(又稱HATEOAS,Hypertext As The Engine of Application State),意味着對于每個響應,REST API提供了如何使用API的所有中繼資料資訊。REST使用這種方式來解耦用戶端和服務端,這樣,API提供者和消費者就可以獨立演進,且不會妨礙它們的通信。
Richardson Maturity Model as a goalpost to achieving truly complete and useful APIs, Source: Kristopher Sandoval
"HATEOAS 是REST的關鍵特性,這也是REST之是以稱為REST的原因。但由于很多人并不使用HATEOAS,導緻他們實際上用的是HTTP RPC",這是Reddit上的一些激進意見。确實,HATEOAS是最成熟的REST版本,但很難實作比通常使用和建構的API用戶端更加進階和智能的API用戶端。是以,即使是如今非常好的REST API也不能保證面面俱到。這也是為什麼HATEOAS主要作為RESTful API設計的長期開發願景。
REST和RPC之間有一些灰色區域,特别是當一個服務具有一部分REST特性,一部分RPC特性時。REST基于資源,而不是基于動作或動詞。
在REST中,會用到像GET, POST, PUT, DELETE, OPTIONS, PATCH這樣的HTTP方法。
解耦用戶端和服務端:REST的抽象比RPC更好,可以更好地解耦用戶端和服務端。具有一定抽象的系統可以更好地封裝其細節并維持其屬性。這使得REST API足夠靈活,可以在保持系統穩定的同時,随時間進行演化。
可發現性:用戶端和服務端的通信描述了所有細節,是以無需額外的文檔來了解如何使用REST API進行互動。
緩存友好:重用了大量HTTP工具,REST是唯一一種允許在HTTP層緩存資料的風格。相比之下,要在其他API風格中實作緩存,則要求配置額外的緩存子產品。
支援多種格式:支援多種格式的資料存儲和互動功能也是使REST成為目前流行的建構公共APIs的原因之一。
沒有單一的REST結構:不存在正确地建構REST API的方式。如何對資源進行模組化,以及對哪些資源模組化取決于具體場景,這使得REST在理論上是簡單的,但實踐上是困難的。
載荷較大:REST會傳回大量中繼資料,是以用戶端可以從響應的資訊中了解到應用的狀态。對于具有大容量帶寬的大型網絡通道來說,這種互動方式沒有問題。但實際情況并不總是這樣,這也是Facebook在2012年推出GraphQL風格的主要驅動因素。
過度擷取和不足擷取問題:由于有時候會出現包含的資料過多或過少的情況,導緻在接收到REST的響應之後,通常還會需要另一個請求。
管理API:專注于管理系統中的對象,并面向多個消費者是最常見的API風格。REST可以幫助這類APIs實作強大的發現能力,良好的文檔記錄,并符合對象模型。
簡單資源驅動的APPs:REST是一種非常有用的方法,可用于連接配接不需要靈活查詢的資源驅動型應用。
它需要多次調用REST API才能傳回所需的内容。 是以,GraphQL被認為是一種改變API規則的風格。
GraphQL 的文法描述了如何發起精确的資料請求。GraphQL适合那些互相之間具有複雜實體引用關系的應用資料模型。
現在,GraphQL生态擴充了相關的庫,并出現了很多強大的工具,如Apollo, GraphiQL, and GraphQL Explorer。
一開始,GraphQL會建立一個schema(模式),它描述了在一個GraphQL API中的所有請求以及這些請求傳回的所有types。構模組化式會比較困難,它需要使用模式定義語言(DSL)進行強類型輸入。
由于在請求前已經建構好了模式,是以用戶端可以對請求進行校驗,確定伺服器能夠進行響應。在到達後端應用後,會有一個GraphQL操作,負責使用前端應用的資料來解析整個模式。在給服務端發送包含大量查詢的請求之後,API會傳回一個JSON響應,内容正對應請求的資源。
除RESTful CRUD操作外,GraphQL還有訂閱功能,允許接收服務端的實時通知。
類型化的模式:GraphQL 會提前釋出它可以做的事情,這種方式提升了可發現性。通過将用戶端指向GraphQL API,我們可以知道哪些查詢是可用的。
非常适合類似圖形的資料:适合深度關聯的資料,不适合扁平資料。
沒有版本控制:最好的版本控制就是不對API進行版本控制。
REST提供了多種API版本,而GraphQL是一種單一的、演化的版本,可以持續通路新的特性,友善服務端代碼的維護。
詳細的錯誤消息:與SOAP類似,GraphQL提供了詳細的錯誤資訊,錯誤資訊包括所有的解析器以及特定的查詢錯誤。
靈活的權限:GraphQL允許在暴露特定的功能的同時保留隐私資訊。而REST架構不能部分展示資料(要麼全部顯示,要麼全部隐藏)。
性能問題:GraphQL用複雜度換來功能上的提升。在一個請求中包含太多封裝的字段可能會導緻系統過載。是以,即時對于複雜的查詢,REST仍然是一個比較好的選擇。
緩存複雜:GraphQL 沒有使用HTTP緩存語義,需要客戶自定義。
需要大量開發前教育訓練:由于沒有足夠的時間弄清楚GraphQL 的基本操作和SDL,很多項目決定沿用REST方式。
手機端API:這種情況下,對網絡性能和單個消息載體的優化非常重要。是以GraphQL為移動裝置提供了一種更有效的資料載體。
複雜系統和微服務:GraphQL能夠将複雜的系統內建隐藏在API背後。從不同的地方聚合資料,并将它們合并成一個全局模式。這對于擴充遺留基礎設施或第三方API尤為重要。
每種API項目都有不同的要求,通常基于如下幾點進行選擇:
使用的程式設計語言
開發環境,以及
涉及的人力和财務資源等
在了解到每種API設計風格之後,API設計者就可以根據項目的需要選擇最合适的API模式。
由于強耦合特性,RPC通常用于内部微服務間的通信,不适用于外部API或API服務。
SOAP比較麻煩,但它本身豐富的安全特性仍然是交易操作、訂單系統和支付等場景的不二之選。
REST具有高度抽象以及最佳的API模型。但往往會增加線路和聊天的負擔--如果使用的是移動裝置,這是不利的一面。
在擷取資料方面,GraphQL邁出了一大步,但并不是所有人都有足夠的時間和精力來處理這種模式。
歸根結底,最好在一些小場景下嘗試每種API風格,然後看是否滿足需求,是否能夠解決問題。如果可以,則可以嘗試擴充到更多的場景。