<b>本文講的是[譯] Coursera 的 GraphQL 之路,</b>
<b></b>
将 GraphQL 添加至 REST + 微服務的後端中
過去的一年中,我們建構了将所有 REST API 動态轉換為 GraphQL 的工具。這使得後端開發者可以繼續編寫他們熟悉的 API,同時用戶端開發者也可以通過 GraphQL 通路所有資料。
本文中将介紹我們的 GraphQL 之旅,特别是過程中的成功及失敗。
Coursera 的 REST API 是基于資源建構的(即課程 API、教師 API、課程成績 API 等)。這樣使得開發和測試都很容易,并且在後端很好地實作了關注分離。然而,随着産品規模擴大以及 API 數量增長,我們開始面臨性能、文檔以及易用性等問題。在許多頁面上,我們發現需要四到五次與伺服器的往返來擷取所有我們需要渲染的資料。
還記得 Facebook 首次推出 GraphQL 時我們團隊非常興奮,因為我們幾乎立刻就意識到 GraphQL 可以解決我們的諸多問題,例如在一次往返擷取所有資料,并為 API 提供結構化的文檔等。雖然我們想馬上停止使用 REST 并開始編寫 GraphQL,但事情并非如此簡單,因為:
當時,Coursera 有超過 1000 個不同的 REST 端點(現在更多),即使我們想完全停止使用 REST,GraphQL 的遷移成本将是極大的。
我們所有的後端服務都使用 REST API 進行服務間通信,是以經常會有給後端服務以及前端提供相同 API 的情況。
我們有三個不同的用戶端(web、iOS 以及 Android),希望能靈活緩慢地推進。
包裝 REST API 是個非常簡單的過程,我們針對下遊 REST 調用通過解析器擷取資料建構了一些實用程式,并寫了一些将現有模型轉為 GraphQL 的規則。
第一步是建構 GraphQL 解析器,然後在生産環境中啟動一個 GraphQL 伺服器,使下遊 REST 調用到源端點。一旦完成了這項工作(用 GraphQL 來驗證一切),我們就會在設定的示範頁面展示資料,幾天之内就可以說 GraphQL 的嘗試成功了。
如果說我從這個項目中學到了一件事,那一定是不要高興太早。
我們的 GraphQL 伺服器完美工作了幾天,但是突然之間,在我們準備給團隊示範之前,每個 GraphQL 查詢都失敗了。我們措手不及,因為自從上次驗證它正常工作以來并沒有對 GraphQL 伺服器進行任何更改。
在調查之後,終于發現由于一個不相關的 bug,下遊課程目錄服務復原到了之前的版本,導緻 GraphQL 中建構的模式不同步了。我們可以手動更新并修複示範頁面,但很快我們意識到當我們的 GraphQL 架構如果擴充到由超過 50 個不同的服務支援的 1000 個不同的資源之後,想保持所有資料都更新到最新幾乎是不可能的。如果在微服務體系中你有多于一個資料來源,那麼問題在于何時,而不是他們是否不同步。
是以我們回到了白闆上,試圖找出一個清晰的解決方案獲得真實資料源。将 REST API 視為真實資料源是有道理的,因為 GraphQL 是基于它們建構的。為此,我們需要自動地确定性地建構 GraphQL 層,以反映目前體系中正在運作的内容,而不是我們認為正在運作的。
基礎架構中的每一個服務都可以動态地提供正在運作的 REST 資源清單。
針對每一個資源,我們可以内省擷取其一系列端點和參數清單(即一個課程可以通過 id 擷取,也可以由講師查找)
隻要發現不同的部分,我們就需要建構一個 GraphQL 模式,在 GraphQL 伺服器上設定一個任務,每五分鐘對所有下遊服務 ping 一次,請求所有資訊。然後,我們就可以在 Pegasus 模式和 GraphQL 類型之間編寫 1:1 的轉化層了。
接下來,我們隻需要簡單定義如何将 GraphQL 查詢轉化為 REST 請求,使用以前的解析器中的大部分邏輯,就可以生成功能完整的 GraphQL 伺服器,不再會過期 5 分鐘以上。
我們希望使用 GraphQL 的一個主要原因是在一個往返中擷取某個頁面需要的所有資料。但是一開始我們的方法隻能提供 REST API 以及 GraphQL 之間一對一的映射。沒有将資源連接配接在一起,我們仍然會像使用 REST API 一樣,使用多次 GraphQL 查詢來擷取資料。雖然通過 GraphQL 擷取使用者資料相比使用 REST 來說,開發者體驗有所提升,但如果在擷取更多資料之前必須等待前序查詢傳回的話,那麼在性能上沒有實質提升。
我們的每個 REST API 都獨立存活,他們不需要知道其他任何 API 的存在。但是,如果使用 GraphQL,模型和資源确實需要彼此的存在,以及如何連接配接。
資源之間的連接配接是不能自動添加的,是以我們定義了一個簡單的标記方法,使得開發者可以添加資源并指定資源之間的關系。例如,我們可以指定一個課程應該有講師字段,代表教授這門課程的講師。擷取這些講師的時候,需要使用 id 查詢,此時就可以使用課程已經提供的<code>instructorIds</code> 字段。我們稱之為「前置關系」,因為我們通過 id 确切知道哪些講師需要擷取。
在想要從一個資源到另一個資源但沒有顯式關聯的情況下,我們添加了反向查詢的支援,也就是擷取一個使用者在一個課程的注冊情況。我們可以在 <code>userEnrollments</code>.v1 資源上通過<code>byCourseId</code> 進行查詢,就可以傳回在指定的課程中指定使用者的注冊資料。
我們開發的文法看起來像這樣:
一旦這些關聯到位,我們的 GraphQL 模式就開始彙集在一起了,不再是小量資料碎片,而是整個 Coursera 資料和資源的網絡。
需要重點提出的是,這種遷移并不以開發效率為代價。我們的前端工程師的确需要學習如何使用 GraphQL,但我們并不需要重寫後端 API 或運作複雜的遷移才能享受 GraphQL 帶來的好處。當建立新的應用程式的時候,它就可供開發人員使用了。
總的來說,我們對 GraphQL 為開發人員(最終為使用者)提供的幫助非常滿意,并對 GraphQL 生态的發展充滿期待。
Coursera 前端基礎設施團隊提供了幫助将 GraphQL 從測試項目轉移至預備生産環境中。
Coursera 的整個工程團隊的耐心以及幫助,我們一起在 GraphQL 層解決了無數 bug 和奇怪的現象。
<b>原文釋出時間為:2017年9月13日</b>
<b>本文來自雲栖社群合作夥伴掘金,了解相關資訊可以關注掘金網站。</b>