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