- 原文地址:Coursera’s journey to GraphQL
- 原文做者:Bryan Kane
- 譯文出自:掘金翻譯計劃
- 本文永久連接:github.com/xitu/gold-m…
- 譯者:bambooom
- 校對者:sunui、alfred-zhong
將 GraphQL 添加至 REST + 微服務的後端中前端
Coursera 的客戶端開發人員喜歡 GraphQL 的靈活性、類型安全性以及社區的支持,這些已經衆所周知。可是,咱們沒有談過多少咱們的後端開發者們對於 GraphQL 的感覺,這是由於實際上他們大多數並不須要爲 GraphQL 考慮太多。react
過去的一年中,咱們構建了將全部 REST API 動態轉換爲 GraphQL 的工具。這使得後端開發者能夠繼續編寫他們熟悉的 API,同時客戶端開發者也能夠經過 GraphQL 訪問全部數據。android
本文中將介紹咱們的 GraphQL 之旅,特別是過程當中的成功及失敗。ios
Coursera 的 REST API 是基於資源構建的(即課程 API、教師 API、課程成績 API 等)。這樣使得開發和測試都很容易,而且在後端很好地實現了關注分離。然而,隨着產品規模擴大以及 API 數量增加,咱們開始面臨性能、文檔以及易用性等問題。在許多頁面上,咱們發現須要四到五次與服務器的往返來獲取全部咱們須要渲染的數據。git
還記得 Facebook 首次推出 GraphQL 時咱們團隊很是興奮,由於咱們幾乎馬上就意識到 GraphQL 能夠解決咱們的諸多問題,例如在一次往返獲取全部數據,併爲 API 提供結構化的文檔等。雖然咱們想立刻中止使用 REST 並開始編寫 GraphQL,但事情並不是如此簡單,由於:github
當時,Coursera 有超過 1000 個不一樣的 REST 端點(如今更多),即便咱們想徹底中止使用 REST,GraphQL 的遷移成本將是極大的。web
咱們全部的後端服務都使用 REST API 進行服務間通訊,因此常常會有給後端服務以及前端提供相同 API 的狀況。後端
咱們有三個不一樣的客戶端(web、iOS 以及 Android),但願能靈活緩慢地推動。api
在一些調查以後,咱們發現了一個引入 GraphQL 的好方法,那就是在 REST API 上添加 GraphQL 的代理層。這個方法實際上也很常見,而且有詳細的文檔驗證過了,因此這裏我就不深刻展開了。安全
包裝 REST API 是個很是簡單的過程,咱們針對下游 REST 調用經過解析器獲取數據構建了一些實用程序,並寫了一些將現有模型轉爲 GraphQL 的規則。
第一步是構建 GraphQL 解析器,而後在生產環境中啓動一個 GraphQL 服務器,使下游 REST 調用到源端點。一旦完成了這項工做(用 GraphQL 來驗證一切),咱們就會在設置的演示頁面展現數據,幾天以內就能夠說 GraphQL 的嘗試成功了。
若是說我從這個項目中學到了一件事,那必定是不要高興太早。
咱們的 GraphQL 服務器完美工做了幾天,可是忽然之間,在咱們準備給團隊演示以前,每一個 GraphQL 查詢都失敗了。咱們措手不及,由於自從上次驗證它正常工做以來並無對 GraphQL 服務器進行任何更改。
在調查以後,終於發現因爲一個不相關的 bug,下游課程目錄服務回滾到了以前的版本,致使 GraphQL 中構建的模式不一樣步了。咱們能夠手動更新並修復演示頁面,但很快咱們意識到當咱們的 GraphQL 架構若是擴展到由超過 50 個不一樣的服務支持的 1000 個不一樣的資源以後,想保持全部數據都更新到最新幾乎是不可能的。若是在微服務體系中你有多於一個數據來源,那麼問題在於什麼時候,而不是他們是否不一樣步。
因此咱們回到了白板上,試圖找出一個清晰的解決方案得到真實數據源。將 REST API 視爲真實數據源是有道理的,由於 GraphQL 是基於它們構建的。爲此,咱們須要自動地肯定性地構建 GraphQL 層,以反映當前體系中正在運行的內容,而不是咱們認爲正在運行的。
幸運的是(也許算有遠見),咱們的 REST 框架給咱們提供了構建這個自動化層須要的一切:
基礎架構中的每個服務均可以動態地提供正在運行的 REST 資源列表。
針對每個資源,咱們能夠內省獲取其一系列端點和參數列表(即一個課程能夠經過 id 獲取,也能夠由講師查找)
另外,咱們接受由 Courier 的模式語言定義的 Pegasus Schemas,用於每一個模型返回數據
只要發現不一樣的部分,咱們就須要構建一個 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 查詢,此時就可使用課程已經提供的 instructorIds
字段。咱們稱之爲「前置關係」,由於咱們經過 id 確切知道哪些講師須要獲取。
在想要從一個資源到另外一個資源但沒有顯式關聯的狀況下,咱們添加了反向查詢的支持,也就是獲取一個用戶在一個課程的註冊狀況。咱們能夠在 userEnrollments
.v1 資源上經過 byCourseId
進行查詢,就能夠返回在指定的課程中指定用戶的註冊數據。
咱們開發的語法看起來像這樣:
courseAPI.addRelation(
"instructors" -> ReverseRelation(
resourceName = "instructors.v1",
finderName = "byCourseId",
arguments = Map("courseId" -> "$id", "version" -> "$version"))複製代碼
一旦這些關聯到位,咱們的 GraphQL 模式就開始聚集在一塊兒了,再也不是小量數據碎片,而是整個 Coursera 數據和資源的網絡。
咱們已經在生產環境中運行 GraphQL 服務器 6 個月了,這條路有時是顛簸的,但咱們切實認識到 GraphQL 帶來的好處。開發人員更容易發現數據及編寫查詢,咱們的產品也因爲 GraphQL 額外提供的類型安全性更加可靠,使用 GraphQL 獲取數據的頁面加載也更快。
須要重點提出的是,這種遷移並不以開發效率爲代價。咱們的前端工程師的確須要學習如何使用 GraphQL,但咱們並不須要重寫後端 API 或運行復雜的遷移才能享受 GraphQL 帶來的好處。當建立新的應用程序的時候,它就可供開發人員使用了。
總的來講,咱們對 GraphQL 爲開發人員(最終爲用戶)提供的幫助很是滿意,並對 GraphQL 生態的發展充滿期待。
掘金翻譯計劃 是一個翻譯優質互聯網技術文章的社區,文章來源爲 掘金 上的英文分享文章。內容覆蓋 Android、iOS、React、前端、後端、產品、設計 等領域,想要查看更多優質譯文請持續關注 掘金翻譯計劃、官方微博、知乎專欄。