Netflix:我們爲什麼要將GraphQL引入前端架構?

在這篇文章中,我們將分享Netflix在這些應用程序的前端架構中引入GraphQL所積累的經驗。

在內部,我們把用於管理廣告創建和組裝的主要應用程序叫作Monet。它用於增強廣告的創建以及自動管理外部廣告平臺上的營銷廣告活動。Monet有助於推動流量增量轉換,增強用戶與產品的互動,並向全世界的用戶展示我們的內容和Netflix品牌。

首先,它有助於擴展和自動化廣告創建以及管理數百萬個廣告素材組合。其次,我們利用各種信號和彙總數據(例如Netflix上的內容流行度)來實現高度相關的廣告。我們的總體目標是使所有外部發布渠道上的廣告都能夠讓用戶產生共鳴,並且不斷嘗試提高有效性。

背景

在剛開始時,Monet的React UI層需要訪問由Tomcat服務器提供的傳統REST API。隨着時間的推移,隨着應用程序的發展,我們的用例變得越來越複雜,即使是一個簡單頁面也需要從各種來源提取數據。

爲了更有效地將這些數據加載到客戶端,我們首先嚐試對後端的數據進行非規範化。但這種非規範化變得難以維護,因爲並非所有頁面都需要所有數據。我們很快遇到了網絡帶寬瓶頸。瀏覽器需要獲取比以往更多的非規範化數據。

爲了減少發送給客戶端的字段數量,一種方法是爲每個頁面構建自定義端點,但這很明顯不是一個好的解決方案。我們沒有去構建自定義端點,而是選擇GraphQL作爲應用程序的中間層。

我們還將Falcor作爲一種可能的解決方案,因爲它在Netflix的很多核心用例中已經取得了很好的成果,並且得到了大量的採用,但因爲GraphQL強大的生態系統和第三方工具,我們認爲GraphQL對我們的用例來說會是更好的選擇。此外,隨着我們的數據結構越來越以圖形爲導向,GraphQL最終會變得更加合適我們的用例。引入GraphQL不僅解決了網絡帶寬瓶頸問題,而且還提供了很多其他優勢,讓我們能夠更快地添加功能。

我們已經在NodeJS上運行GraphQL大約6個月,並且它已經被證明可以顯著提高我們的開發速度和整體頁面的加載性能。以下是從我們開始使用它以來給我們帶來的一些好處。

GraphQL優點

重新分配負載和優化有效載荷

通常,某些機器比其他機器更適合用來完成某些任務。當我們引入GraphQL中間層後,GraphQL服務器仍然需要調用與客戶端相同的服務和REST API。現在的區別在於,大多數數據是在同一數據中心內的服務器之間流動。這些服務器到服務器之間的調用具有非常低的延遲,而且帶寬非常高,與來自瀏覽器的直接網絡調用相比,性能提升了8倍。

從GraphQL服務器到客戶端瀏覽器的最後一英里數據傳輸現在減少到了單個網絡調用。由於GraphQL允許客戶端選擇它需要的數據,所以我們只需要獲取更小的有效載荷。

在我們的應用程序中,之前需要獲取10MB數據的頁面現在只需要獲取200KB。頁面加載速度變得更快,特別是在數據受限的移動網絡上,我們的應用程序使用的內存也更少了。這些變更是以提高服務器利用率爲代價的,服務器需要進行數據的獲取和聚合,不過雖然犧牲了這額外的幾毫秒服務器時間,卻換來了較小的客戶端有效載荷。

可重用的抽象

軟件開發人員通常希望使用可重用的抽象而不是單一用途的方法。在使用GraphQL時,我們定義了數據以及與數據之間的關係。當消費者應用程序從多個源獲取數據時,不需要操心與數據連接操作相關聯的複雜業務邏輯。

例如,我們只在GraphQL中定義一次實體:catalog、creative和comment。我們現在可以基於這些定義構建多個頁面視圖。客戶端應用程序(catalogView)的一個頁面想要獲得catalog中所有creative的所有comment,而另一個客戶端頁面(creativeView)想要知道creative和相關comment所屬的catalog。

鏈接類型系統

很多人專注於單一服務中的類型系統,但很少關注跨服務。在GraphQL服務中定義了實體之後,我們就會使用自動生成工具爲客戶端應用程序生成TypeScript類型。React組件的prop接收類型以匹配組件查詢。由於這些類型和查詢也需要通過服務器schema的驗證,因此服務器的任何重大更改都將被使用數據的客戶端捕獲到。

使用GraphQL將多個服務鏈接在一起,並將這些檢查過程集成到構建過程中,可以在部署錯誤代碼之前捕獲更多問題。理想情況下,我們可以實現從數據庫層一直到客戶端瀏覽器的類型安全性。

DI/DX——簡化開發

創建客戶端應用程序時人們比較關心的是UI/UX,但開發者接口和開發者體驗對於構建可維護應用程序來說同樣重要。在使用GraphQL之前,編寫一個新的React容器組件需要維護複雜的邏輯。

開發人員需要考慮數據之間的相關性、如何緩存數據、是否進行並行調用還是串行調用以及在Redux的什麼地方保存數據。在使用GraphQL查詢包裝器後,每個React組件只需要描述它需要的數據,包裝器會處理所有其他問題。這樣樣板代碼更少了,數據和UI之間的關注點也更清晰了。這種聲明性數據提取模型讓React組件變得更容易理解。

其他好處

我們也注意到了其他的一些較小的好處。首先,如果GraphQL查詢的某些解析器失敗,成功的解析器仍然會將數據返回到客戶端,並儘可能多地渲染頁面。其次,後端數據模型得到大大的簡化,因爲我們不需要關心客戶端的模型,並且在大多數情況下可以只提供原始實體的CRUD接口。最後,測試組件也變得更容易,因爲GraphQL查詢被自動轉換爲測試存根,我們可以獨立於React組件測試解析器。

成長的煩惱

基於我們爲網絡請求和轉換數據構建的大多數基礎設施,可以輕鬆地將React應用程序轉到NodeJS服務器上,無需更改任何代碼。我們刪除的代碼比我們添加的還要多。但是,在遷移過程中,我們難免要克服一些障礙。

自私的解析器

GraphQL中的解析器作爲獨立單元運行,不需要關心其他解析器,因此我們發現它們對相同或類似的數據進行了很多重複的網絡請求。我們通過爲數據提供者提供一個簡單的緩存層來解決這種重複問題,這個緩存層將網絡響應保存在內存中,直到所有解析器處理完畢。緩存層還允許我們將對單個服務的多個請求聚合成一次性請求。解析器現在可以請求它們需要的任何數據,無需操心如何進行優化數據的獲取過程。

我們編織的糟糕的Web

抽象是提高開發人員效率的好辦法…直到出現問題。毫無疑問,我們的代碼中存在bug,我們不希望中間層將bug的根源隱藏掉。GraphQL會自動編排對其他服務的網絡調用,從而隱藏用戶的複雜性。服務器日誌可以作爲一種調試方法,但仍然不能使用瀏覽器實現這一步的調試。爲了使調試更容易,我們將日誌直接添加到GraphQL響應有效載荷中,顯示了服務器正在處理的所有網絡請求。在啓用調試標誌後,你將在瀏覽器中獲得與使用瀏覽器進行網絡調用時相同的數據。

被破壞的類型

GraphQL破壞了OOP的範式,當我們獲取部分對象時,這些數據不能用於需要完整對象的方法和組件。當然,你可以手動轉換這些對象,但是你會失去類型系統的很多好處。所幸的是,TypeScript支持動態類型,因此我們可以調整方法,讓它們只需要獲得真正需要的對象屬性即可。定義這些更精確的類型需要更多的工作量,但總體上能夠提供更大的類型安全性。

接下來

我們仍然處於探索GraphQL的早期階段,到目前爲止,這是一次積極的體驗,我們很高興能夠採用它。我們的關鍵目標之一是在系統變得日益複雜的同時幫助我們提高開發效率。

我們希望在圖數據模型上進行投入,以便隨着更多邊和節點的增加,我們的團隊將變得更加高效,而不是陷入複雜數據結構的泥潭中。在過去的幾個月,我們已經發現現有的圖模型已經變得足夠強大,我們不需要修改圖就可以構建出一些功能。GraphQL確實使我們更有成效。

隨着GraphQL繼續發展和不斷成熟,我們期待從社區學習到更多東西。在實現層面,我們期待使用一些很酷的概念,如模式拼接,這讓與其他服務的集成變得更加簡單,並節省大量的開發時間。最重要的是,讓公司的更多團隊看到GraphQL的潛力並開始採用它是一件非常令人興奮的事情。

英文原文

https://medium.com/netflix-techblog/our-learnings-from-adopting-graphql-f099de39ae5f

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章