2015年,Facebook推出了GraphQL(Graph-Query-Language)查詢語言。到目前爲止,IBM、Twitter、Walmart Labs、紐約時報、Coursera等很多公司已經在內部從RESTful轉向GraphQL API。
作爲一種查詢語言,GraphQL具有以下特點:
(1)無需關心如何更新文檔,所有的查詢(query)和變更會自動形成文檔(cchema)。
(2)無需獲取整個數據集,通過schema與resolver(處理器)之間的映射關係,由對應的resolver去獲取數據,將結果返回給前端,從而可以編寫僅僅返回所請求數據的查詢。
(3)對前端提供統一的訪問點。從不同的API中獲取數據並非易事,GraphQL支持將所有API進行拼接。
愛奇藝號技術團隊在實施微服務化的過程中,受到Forrester Research提出的低代碼開發(Low-Code:即無需編碼或通過少量代碼就可以快速生成應用程序的開發理念)的啓發,基於GraphQL構建BFF(Backend for Frontends),幫助開發人員用拖拽式操作,直觀地創建出一個供前端調用的API,本文將對實施過程中的經驗總結進行敘述。
與 RESTful API 一樣,GraphQL API設計用於處理 HTTP 請求併爲這些請求提供響應。REST API 構建在請求方法和端點之間的連接上,而 GraphQL API 被設計爲只通過一個端點,始終使用 POST 請求進行查詢,其 URL 通常是xxx.com/graphql。圖1-1爲GraphQL部署架構圖,可以看到它處於系統“中間層”。
GraphQL 全稱叫 Graph Query Language,官方宣傳語是“爲你的 API 量身定製的查詢語言”,用傳統的方式來解釋就是:將你所有後端 API 組成的集合看成一個數據庫,用戶終端發送一個查詢語句,你的 GraphQL 服務解析這條語句並通過一系列規則從你的“API 數據庫”裏面將查詢的數據結果返回給終端,而 GraphQL 就相當於這個系統的一個查詢語言,像 SQL 之於 MySQL 一樣。GraphQL執行過程如圖1-2所示:
圖1-2是GraphQL 執行的大致流程,第一步去驗證查詢語句是否符合GraphQL的schema規範,確認查詢內容的合法性,第二步生成執行的上下文,關鍵點在第三步和第四步,第三步是獲取查詢語句所需要查詢的字段,這裏叫 fields,所有需要查詢的字段可以在查詢語句裏拿到,這就是 GraphQL 如何做到避免返回冗餘數據的。拿到所有需要查詢的字段後,第四步針對每一個字段去執行它的 resolver,可以從 resolver 返回的數據裏面拿到字段對應的數據,最後是格式化結果並返回。
重點是第四步,展開說明一下,如圖1-3、圖1-4所示:
在GraphQL裏面有一個概念叫類型 (type),每一個類型下面對應的是一個或多個字段(field),每個字段都會綁定一個處理器(resolver),這個 resolver 的作用就是獲取字段對應的數據。
對應到圖1-4所示,UserInfo這個類型,它有三個字段:nickName、contractNo、fansNumber。每個字段都對應一個resolver,resolver 需要被開發者重新定義,否則會報錯。所以UserInfo下的三個字段nickName、contractNo、fansNumber需要通過實現各自resolver來分別從用戶微服務、合同微服務、粉絲微服務去獲取用戶信息、合同信息和粉絲信息,然後再聚合返回,這樣就達到了使用 GraphQL 進行數據拼接的目的。
愛奇藝號在實施微服務化的過程中,加入了BFF的前後端架構,如圖2-1所示:
從圖中可以看出,BFF作爲前後端的中間層服務。主要的業務邏輯都封裝在BFF層,前端通過BFF進行訪問,減少微服務之間的相互調用。BFF層通過REST API方式提供服務,隨着服務的增多,提供的接口越來越多,這會導致REST API越來越冗餘。對於前端而言,有的API粒度較粗不滿足需求;有的API又粒度太細,不僅增加了響應時間,還會造成流量的浪費。對於後端而言,前端需要的數據往往在不同的地方具有相似性,但卻又不盡相同,比如:針對用戶信息,有些地方需要用戶簡要的基礎信息和詳細的視頻信息,而有些地方卻需要用戶詳細的基礎信息和簡要的視頻信息。這往往需要開發不同的接口去滿足各種定製需求,增加了開發人員的工作量,提升了開發工作的重複度。
GraphQL與Rest API對比:
|
性能 |
文檔 |
調試 |
學習成本 |
GraphQL |
性能好,申明式獲取,非常直觀和精準,減少不必要數據網絡傳輸 |
代碼即文檔 |
前端能快速感知,強類型,避免出錯 |
高 |
Rest API |
缺乏彈性,大多數情況會獲取額外數據 |
使用Swagger等 |
後端配合排查 |
低 |
從表2-1中的對比可以看出,GraphQL相對於Rest API方式,性能更好,能有效減少前後端開發溝通成本。但是Facebook的官方只有JS版本實現,查詢方式和Rest API也有所不同(如表2-2所示),對於老項目有一定的遷移、學習成本。
接下來,本文將主要探討如何基於graphql-java,做到減少遷移成本的同時,又能提升後端開發人員的效率,避免重複開發。
愛奇藝號API生成平臺,是一個低代碼平臺。
由於愛奇藝號的技術棧主要基於Java,所以使用的是GraphQL的 Java實現。
基於graphql-java,API生成平臺主要做了以下功能優化及增強。
(2)動態接入監控:動態生成的API,與其他普通接口一樣支持Prometheus監控,保證監控的靈活性和服務的穩定性。
(3)靈活配置:可以動態生成GraphQL的schema,方便後端接入新服務。
(4)可視化API管理平臺:API接口提供可視化操作,方便查看、新增、修改和重用。
graphql-java通過Spring的封裝,位於整個架構的網關層或BFF層。項目user-info-graphQL依賴graphql-java-spring,支持Rest API請求。平臺的整體架構圖如圖3-1所示:
User-info-graphQL的服務流程圖如圖3-2所示。客戶端通過graphql/前綴的Rest API方式請求,後端通過前綴與GraphQL Query綁定,從DB獲取映射關係,最終轉換成GraphQL支持的查詢語法。
在user-info-graphQL項目中,原本是通過template url來匹配任意自定義url;導致監控平臺只能顯示template url的請求信息,如圖3-3所示。這個問題可以通過重寫spring-boot-actuator中獲取tags的方法,將真實的url請求信息暴露到Spring boot的/actuator/prometheus端點這個方法來解決,如圖3-4所示。
通過暴露的監控端點接入Prometheus,實現對新生成的API進行實時動態監控,示例效果如圖3-5所示:
爲了方便後端快速接入新增微服務,達到支持API動態擴展目的。項目中通過Velocity定義schema模板,通過Java註解、反射機制動態生成graphqls模板文件,如圖3-6所示:
圖3-6中的GraphQL schema模板,支持通過用戶UID查詢用戶信息。用戶信息是由多個微服務聚合而成,採用異步調用多個微服務並行獲取數據。基於此模板,用戶只需要實現SPI定義好的接口,就能實現對新增微服務的支持。
通過API生成管理平臺,開發人員可以實現API接口的可視化配置、生成、動態監控等功能,達到開箱即用的效果,極大提升開發和運維效率。
通過GraphQL動態構建BFF服務層API,聚合不同的微服務,相比於Rest API方式,能夠減少後端重複開發,加快響應前端需求。後端開發人員只需要開發維護新增微服務,並通過SPI方式,增加BFF層對新增微服務的支持即可。
通過在愛奇藝號後端微服務引入GraphQL構建BFF服務層,可以達成以下效果:
-
便於監控:新增BFF層API接口,通過支持Prometheus端點監控,無開發成本。
-
支持系統高吞吐量:BFF服務、微服務都是基於docker部署,支持QPS動態擴容,能夠支持高併發。
-
便於維護和擴展:基於GraphQL構建的BFF層,API接口動態生成,層次清晰,更易維護、擴展。
未來規劃:
隨着BFF端對API請求的多樣化,需要動態支持新方法擴展及監控。
目前API與請求的映射關係持久化在MySQL中,需要支持集羣和高性能,後續逐步遷移到ZK或Redis中,並緩存到本地。
隨着雲原生和K8s的興起,基於K8s部署的Go服務,更易擴容和維護。基於Java實現的GraphQL,如果遷移到K8s上部署,很難實現快速擴縮容的效果。而graphql-go在github上的star高達7k,可見熱度極高。如果基於Go實現BFF端,API與請求的映射關係可以存儲於K8s的Pod配置文件中,並且通過一個API部署一類Pod,進行服務隔離,可以更高程度的保證服務穩定。
掃一掃下方二維碼,更多精彩內容陪伴你!