[譯] 關於使用 GRAPHQL 構建專案的回顧

Yuqi發表於2018-12-18

在 2016 年末,我們決定用 Python 和 React 重寫老舊的 PHP 遺留系統。由於只有四個月的時間在 2017 年的節日(到來前)及時建立 MVP(模式開發的系統),我們必須非常謹慎地決定如何投入時間。

我們投入使用的技術之一就是 GraphQL。我們中之前還從來沒有人用過它,但我們認為它對於快速交付以及能讓人們獨立工作至關重要。

事實證明這是一個非常好的決定,所以兩年後我們想回顧並分享從那時起學到的東西...

兩年後的 GraphQL

我們從遺留系統學到的教訓,大大影響了我們,於是我們決定使用 GraphQL。我們在相當數量的微服務之間使用 REST APIS,導致很多混亂,如不相容的介面,不同的資源識別符號和非常複雜的部署。任何 API 的變動都需要同時部署所有使用了這個 API 的服務以避免停機故障,這會經常出現錯誤並導致很長的釋出週期。在單個 API 閘道器使用 GraphQL,我們將可以大大簡化服務格局。我們也決定了使用 Relay,它為我們提供了一種識別資源的單一的、全域性的方式,以及組織 GraphQL 模型的簡單方法。

我們使用單一服務作為 GraphQL 伺服器,它反過來會請求各種後端服務 -- 其中大部分是 REST APIs,但是因為它們都只和閘道器通訊,所以它們可以使用任何想用的介面。閘道器被設計為完全無狀態的,這對於可擴充套件性大有裨益。快取也是在 GraphQL 閘道器中,因此,只需擴大閘道器例項的數量,就可以輕鬆擴充套件整個系統。

API 閘道器並不是 GraphQL 世界的規章,所以為什麼儘管使用它們意味著需要從閘道器到後臺服務的附加請求,我們還要使用呢?對於我們而言,最大的原因就是減少 API 的相互依賴。沒有閘道器的話,我們的服務結構將會差不多像這樣:

[譯] 關於使用 GRAPHQL 構建專案的回顧

很多服務都和很多其他服務互通,導致需要大量的 API 連線,連線數量會以大致相當於服務數量的二次方的速度增長。這不但幾乎不可能讓任何人記住,同時還在處理中斷,維護和 API 更改時,增加了大量複雜性。

即使在這樣的網路中,GraphQL 也能夠幫助提升向後相容性,但這是當你在服務之間放置一個單一閘道器的時候所會發生的事情:

[譯] 關於使用 GRAPHQL 構建專案的回顧

忽然間,就僅有線性數量的連線了,每個新的服務僅會在網路圖中增加一個新連線。API 變化僅需要影響它的源服務和閘道器。

API 閘道器是服務互相通訊的唯一途徑,這就大大降低了複雜度。它還為快取,縮放,監視和分析建立了一個很好的中心位置。一般來說,只有一項服務負責這麼多事情並不是一個好主意 -- 而是一個故障點。

但是,API 閘道器是無狀態的。它沒有資料庫,沒有本地資源也沒有認證。這意味著它可以在水平方向上縮放自如,同時因為它還負責了快取,所以僅增加閘道器例項的數量就有助於顯著解決流量高峰(的問題)。

當然,閘道器也不是全無代價的:一個請求現在要傳送兩次了,並且如果一個後臺服務想要和另一個後臺服務通訊,就必須通過閘道器。這對於建立一個更易於維護的中心介面非常有用,但是對於效能來說並不是很好。這就是無狀態閘道器展現自己光輝的時候。因為閘道器程式碼在哪裡執行並不重要,那就沒有什麼能阻止我們將每個後端服務都作為其自己的閘道器。我們將 GraphQL 介面移動到了每個服務中,直接傳送網路請求,而不是傳送兩次,這樣完全不需要使用 GraphQL 伺服器,但是卻依舊保留了所有 GraphQL 中心模型的優勢。並且由於我們使用了 Python 定義了 GraphQL 模型,我們決定更深一步,通過從 GraphQL 模型中自動生成 API 包裝器,可以直接在 Python 中使用它。

結果就是現在服務間的通訊程式碼變成了如下這樣:

GraphQL Code

GraphQL 模型的 API 包裹器是完全從 graphene schema 自動生成的。所以,服務甚至不需要模型檔案的副本。沒有多餘的請求,身份驗證在後臺透明處理,欄位在訪問時會被延遲解析。

現在,在這樣的環境中,成為一個好的 API “公民”就會有一些要求了。後臺 API 大多可以做任何它們想做的事情,但是在如何進行快取和許可權檢查的時候它們必須發揮很好的作用。我們在後端 APIs 中使用的規則如下:

避免巢狀物件,僅返回相關聯物件的 IDs

在 REST API 中返回巢狀的物件是減少請求數量的一個很好的方法。但是這也讓快取非常困難,並可能導致獲取多餘的資源,這正是 GraphQL 應該對抗的。通常情況下,我們避免大的,複雜的請求,而更偏向於稍多但是容易快取的,更加扁平化的請求。

如果確實需要巢狀,絕不巢狀那些有附加許可權的物件

有時候效能要求超過了簡單性的要求,那我們就可以返回潛逃的物件,例如,在一個 API 應答中包含一個相關聯的巢狀物件的長列表。但是,我們只在被巢狀物件的許可權不比外層物件更嚴格的情況下這樣做,因為如果不這樣,應答就無法被快取。

我們使用 graphene 和 graphene-django 來實際執行伺服器,我們不使用 graphene-django 自動對映 Django 模型的能力,因為所有的資料都來自外部請求,我們只使用它來與我們的堆疊的其餘部分相容並熟悉它。整個閘道器服務實際上就是一個單獨的 GraphQLView,我們做了一點小小的擴充來允許我們對前端做出優化:

  • 我們將報錯資訊優化,用以將 Django REST Framework 錯誤從後端服務中分解出來。DRF 每個欄位可能有不止一個錯誤,但它在原生 graphene-django 中並不起作用,所以我們擴充套件了檢視,用來為每個欄位提供精確的錯誤資訊。
  • 我們擴充套件了錯誤日誌,以便更容易地報告各種錯誤資訊。例如 4xx 錯誤實際意味著使用者錯誤,但是由於閘道器呼叫了另一個不同的 API,它同樣也意味著閘道器錯誤的使用了我們的 API。DRF 不會記錄後臺服務的 4xx 錯誤,因此,當實際上是我們而不是使用者導致的錯誤時,我們會在閘道器中執行此操作。
  • 監控:GraphQLView 是新增各種效能監控位的絕佳位置。我們追蹤每個請求的執行時間,對查詢進行雜湊,以便合計不同引數的同一請求的響應時間。

GraphQL 對我們大有益處,但是我們也犯了很多錯誤。有時候我們會努力保持我們的 API 能真正向後相容,除了效能監控和更好的錯誤報告,還必須為已棄用的欄位投入額外的監控。每次 API 變化就需要手動更新 GraphQL 模型,這是相當乏味的事情;並且為了通過 GraphQL 使後臺服務的通訊變得非常容易,我們有時也會打破一些服務的邊界。但最終,它幫助我們更快地發展,維持了基礎設施核心模型的簡易,並使我們的團隊更加自動化。

如果發現譯文存在錯誤或其他需要改進的地方,歡迎到 掘金翻譯計劃 對譯文進行修改並 PR,也可獲得相應獎勵積分。文章開頭的 本文永久連結 即為本文在 GitHub 上的 MarkDown 連結。


掘金翻譯計劃 是一個翻譯優質網際網路技術文章的社群,文章來源為 掘金 上的英文分享文章。內容覆蓋 AndroidiOS前端後端區塊鏈產品設計人工智慧等領域,想要檢視更多優質譯文請持續關注 掘金翻譯計劃官方微博知乎專欄

相關文章