Netflix:我们为什么要将GraphQL引入前端架构?\n
在這篇文章中,我們將分享Netflix在這些應(yīng)用程序的前端架構(gòu)中引入GraphQL所積累的經(jīng)驗(yàn)。
在內(nèi)部,我們把用于管理廣告創(chuàng)建和組裝的主要應(yīng)用程序叫作Monet。它用于增強(qiáng)廣告的創(chuàng)建以及自動(dòng)管理外部廣告平臺(tái)上的營(yíng)銷廣告活動(dòng)。Monet有助于推動(dòng)流量增量轉(zhuǎn)換,增強(qiáng)用戶與產(chǎn)品的互動(dòng),并向全世界的用戶展示我們的內(nèi)容和Netflix品牌。
首先,它有助于擴(kuò)展和自動(dòng)化廣告創(chuàng)建以及管理數(shù)百萬(wàn)個(gè)廣告素材組合。其次,我們利用各種信號(hào)和匯總數(shù)據(jù)(例如Netflix上的內(nèi)容流行度)來(lái)實(shí)現(xiàn)高度相關(guān)的廣告。我們的總體目標(biāo)是使所有外部發(fā)布渠道上的廣告都能夠讓用戶產(chǎn)生共鳴,并且不斷嘗試提高有效性。
背景
在剛開(kāi)始時(shí),Monet的React UI層需要訪問(wèn)由Tomcat服務(wù)器提供的傳統(tǒng)REST API。隨著時(shí)間的推移,隨著應(yīng)用程序的發(fā)展,我們的用例變得越來(lái)越復(fù)雜,即使是一個(gè)簡(jiǎn)單頁(yè)面也需要從各種來(lái)源提取數(shù)據(jù)。
為了更有效地將這些數(shù)據(jù)加載到客戶端,我們首先嘗試對(duì)后端的數(shù)據(jù)進(jìn)行非規(guī)范化。但這種非規(guī)范化變得難以維護(hù),因?yàn)椴⒎撬许?yè)面都需要所有數(shù)據(jù)。我們很快遇到了網(wǎng)絡(luò)帶寬瓶頸。瀏覽器需要獲取比以往更多的非規(guī)范化數(shù)據(jù)。
為了減少發(fā)送給客戶端的字段數(shù)量,一種方法是為每個(gè)頁(yè)面構(gòu)建自定義端點(diǎn),但這很明顯不是一個(gè)好的解決方案。我們沒(méi)有去構(gòu)建自定義端點(diǎn),而是選擇GraphQL作為應(yīng)用程序的中間層。
我們還將Falcor作為一種可能的解決方案,因?yàn)樗贜etflix的很多核心用例中已經(jīng)取得了很好的成果,并且得到了大量的采用,但因?yàn)镚raphQL強(qiáng)大的生態(tài)系統(tǒng)和第三方工具,我們認(rèn)為GraphQL對(duì)我們的用例來(lái)說(shuō)會(huì)是更好的選擇。此外,隨著我們的數(shù)據(jù)結(jié)構(gòu)越來(lái)越以圖形為導(dǎo)向,GraphQL最終會(huì)變得更加合適我們的用例。引入GraphQL不僅解決了網(wǎng)絡(luò)帶寬瓶頸問(wèn)題,而且還提供了很多其他優(yōu)勢(shì),讓我們能夠更快地添加功能。
我們已經(jīng)在NodeJS上運(yùn)行GraphQL大約6個(gè)月,并且它已經(jīng)被證明可以顯著提高我們的開(kāi)發(fā)速度和整體頁(yè)面的加載性能。以下是從我們開(kāi)始使用它以來(lái)給我們帶來(lái)的一些好處。
GraphQL優(yōu)點(diǎn)
重新分配負(fù)載和優(yōu)化有效載荷
通常,某些機(jī)器比其他機(jī)器更適合用來(lái)完成某些任務(wù)。當(dāng)我們引入GraphQL中間層后,GraphQL服務(wù)器仍然需要調(diào)用與客戶端相同的服務(wù)和REST API?,F(xiàn)在的區(qū)別在于,大多數(shù)數(shù)據(jù)是在同一數(shù)據(jù)中心內(nèi)的服務(wù)器之間流動(dòng)。這些服務(wù)器到服務(wù)器之間的調(diào)用具有非常低的延遲,而且?guī)挿浅8?#xff0c;與來(lái)自瀏覽器的直接網(wǎng)絡(luò)調(diào)用相比,性能提升了8倍。
從GraphQL服務(wù)器到客戶端瀏覽器的最后一英里數(shù)據(jù)傳輸現(xiàn)在減少到了單個(gè)網(wǎng)絡(luò)調(diào)用。由于GraphQL允許客戶端選擇它需要的數(shù)據(jù),所以我們只需要獲取更小的有效載荷。
在我們的應(yīng)用程序中,之前需要獲取10MB數(shù)據(jù)的頁(yè)面現(xiàn)在只需要獲取200KB。頁(yè)面加載速度變得更快,特別是在數(shù)據(jù)受限的移動(dòng)網(wǎng)絡(luò)上,我們的應(yīng)用程序使用的內(nèi)存也更少了。這些變更是以提高服務(wù)器利用率為代價(jià)的,服務(wù)器需要進(jìn)行數(shù)據(jù)的獲取和聚合,不過(guò)雖然犧牲了這額外的幾毫秒服務(wù)器時(shí)間,卻換來(lái)了較小的客戶端有效載荷。
可重用的抽象
軟件開(kāi)發(fā)人員通常希望使用可重用的抽象而不是單一用途的方法。在使用GraphQL時(shí),我們定義了數(shù)據(jù)以及與數(shù)據(jù)之間的關(guān)系。當(dāng)消費(fèi)者應(yīng)用程序從多個(gè)源獲取數(shù)據(jù)時(shí),不需要操心與數(shù)據(jù)連接操作相關(guān)聯(lián)的復(fù)雜業(yè)務(wù)邏輯。
例如,我們只在GraphQL中定義一次實(shí)體:catalog、creative和comment。我們現(xiàn)在可以基于這些定義構(gòu)建多個(gè)頁(yè)面視圖。客戶端應(yīng)用程序(catalogView)的一個(gè)頁(yè)面想要獲得catalog中所有creative的所有comment,而另一個(gè)客戶端頁(yè)面(creativeView)想要知道creative和相關(guān)comment所屬的catalog。
鏈接類型系統(tǒng)
很多人專注于單一服務(wù)中的類型系統(tǒng),但很少關(guān)注跨服務(wù)。在GraphQL服務(wù)中定義了實(shí)體之后,我們就會(huì)使用自動(dòng)生成工具為客戶端應(yīng)用程序生成TypeScript類型。React組件的prop接收類型以匹配組件查詢。由于這些類型和查詢也需要通過(guò)服務(wù)器schema的驗(yàn)證,因此服務(wù)器的任何重大更改都將被使用數(shù)據(jù)的客戶端捕獲到。
使用GraphQL將多個(gè)服務(wù)鏈接在一起,并將這些檢查過(guò)程集成到構(gòu)建過(guò)程中,可以在部署錯(cuò)誤代碼之前捕獲更多問(wèn)題。理想情況下,我們可以實(shí)現(xiàn)從數(shù)據(jù)庫(kù)層一直到客戶端瀏覽器的類型安全性。
DI/DX——簡(jiǎn)化開(kāi)發(fā)
創(chuàng)建客戶端應(yīng)用程序時(shí)人們比較關(guān)心的是UI/UX,但開(kāi)發(fā)者接口和開(kāi)發(fā)者體驗(yàn)對(duì)于構(gòu)建可維護(hù)應(yīng)用程序來(lái)說(shuō)同樣重要。在使用GraphQL之前,編寫一個(gè)新的React容器組件需要維護(hù)復(fù)雜的邏輯。
開(kāi)發(fā)人員需要考慮數(shù)據(jù)之間的相關(guān)性、如何緩存數(shù)據(jù)、是否進(jìn)行并行調(diào)用還是串行調(diào)用以及在Redux的什么地方保存數(shù)據(jù)。在使用GraphQL查詢包裝器后,每個(gè)React組件只需要描述它需要的數(shù)據(jù),包裝器會(huì)處理所有其他問(wèn)題。這樣樣板代碼更少了,數(shù)據(jù)和UI之間的關(guān)注點(diǎn)也更清晰了。這種聲明性數(shù)據(jù)提取模型讓React組件變得更容易理解。
其他好處
我們也注意到了其他的一些較小的好處。首先,如果GraphQL查詢的某些解析器失敗,成功的解析器仍然會(huì)將數(shù)據(jù)返回到客戶端,并盡可能多地渲染頁(yè)面。其次,后端數(shù)據(jù)模型得到大大的簡(jiǎn)化,因?yàn)槲覀儾恍枰P(guān)心客戶端的模型,并且在大多數(shù)情況下可以只提供原始實(shí)體的CRUD接口。最后,測(cè)試組件也變得更容易,因?yàn)镚raphQL查詢被自動(dòng)轉(zhuǎn)換為測(cè)試存根,我們可以獨(dú)立于React組件測(cè)試解析器。
成長(zhǎng)的煩惱
基于我們?yōu)榫W(wǎng)絡(luò)請(qǐng)求和轉(zhuǎn)換數(shù)據(jù)構(gòu)建的大多數(shù)基礎(chǔ)設(shè)施,可以輕松地將React應(yīng)用程序轉(zhuǎn)到NodeJS服務(wù)器上,無(wú)需更改任何代碼。我們刪除的代碼比我們添加的還要多。但是,在遷移過(guò)程中,我們難免要克服一些障礙。
自私的解析器
GraphQL中的解析器作為獨(dú)立單元運(yùn)行,不需要關(guān)心其他解析器,因此我們發(fā)現(xiàn)它們對(duì)相同或類似的數(shù)據(jù)進(jìn)行了很多重復(fù)的網(wǎng)絡(luò)請(qǐng)求。我們通過(guò)為數(shù)據(jù)提供者提供一個(gè)簡(jiǎn)單的緩存層來(lái)解決這種重復(fù)問(wèn)題,這個(gè)緩存層將網(wǎng)絡(luò)響應(yīng)保存在內(nèi)存中,直到所有解析器處理完畢。緩存層還允許我們將對(duì)單個(gè)服務(wù)的多個(gè)請(qǐng)求聚合成一次性請(qǐng)求。解析器現(xiàn)在可以請(qǐng)求它們需要的任何數(shù)據(jù),無(wú)需操心如何進(jìn)行優(yōu)化數(shù)據(jù)的獲取過(guò)程。
我們編織的糟糕的Web
抽象是提高開(kāi)發(fā)人員效率的好辦法…直到出現(xiàn)問(wèn)題。毫無(wú)疑問(wèn),我們的代碼中存在bug,我們不希望中間層將bug的根源隱藏掉。GraphQL會(huì)自動(dòng)編排對(duì)其他服務(wù)的網(wǎng)絡(luò)調(diào)用,從而隱藏用戶的復(fù)雜性。服務(wù)器日志可以作為一種調(diào)試方法,但仍然不能使用瀏覽器實(shí)現(xiàn)這一步的調(diào)試。為了使調(diào)試更容易,我們將日志直接添加到GraphQL響應(yīng)有效載荷中,顯示了服務(wù)器正在處理的所有網(wǎng)絡(luò)請(qǐng)求。在啟用調(diào)試標(biāo)志后,你將在瀏覽器中獲得與使用瀏覽器進(jìn)行網(wǎng)絡(luò)調(diào)用時(shí)相同的數(shù)據(jù)。
被破壞的類型
GraphQL破壞了OOP的范式,當(dāng)我們獲取部分對(duì)象時(shí),這些數(shù)據(jù)不能用于需要完整對(duì)象的方法和組件。當(dāng)然,你可以手動(dòng)轉(zhuǎn)換這些對(duì)象,但是你會(huì)失去類型系統(tǒng)的很多好處。所幸的是,TypeScript支持動(dòng)態(tài)類型,因此我們可以調(diào)整方法,讓它們只需要獲得真正需要的對(duì)象屬性即可。定義這些更精確的類型需要更多的工作量,但總體上能夠提供更大的類型安全性。
接下來(lái)
我們?nèi)匀惶幱谔剿鱃raphQL的早期階段,到目前為止,這是一次積極的體驗(yàn),我們很高興能夠采用它。我們的關(guān)鍵目標(biāo)之一是在系統(tǒng)變得日益復(fù)雜的同時(shí)幫助我們提高開(kāi)發(fā)效率。
我們希望在圖數(shù)據(jù)模型上進(jìn)行投入,以便隨著更多邊和節(jié)點(diǎn)的增加,我們的團(tuán)隊(duì)將變得更加高效,而不是陷入復(fù)雜數(shù)據(jù)結(jié)構(gòu)的泥潭中。在過(guò)去的幾個(gè)月,我們已經(jīng)發(fā)現(xiàn)現(xiàn)有的圖模型已經(jīng)變得足夠強(qiáng)大,我們不需要修改圖就可以構(gòu)建出一些功能。GraphQL確實(shí)使我們更有成效。
隨著GraphQL繼續(xù)發(fā)展和不斷成熟,我們期待從社區(qū)學(xué)習(xí)到更多東西。在實(shí)現(xiàn)層面,我們期待使用一些很酷的概念,如模式拼接,這讓與其他服務(wù)的集成變得更加簡(jiǎn)單,并節(jié)省大量的開(kāi)發(fā)時(shí)間。最重要的是,讓公司的更多團(tuán)隊(duì)看到GraphQL的潛力并開(kāi)始采用它是一件非常令人興奮的事情。
英文原文
https://medium.com/netflix-techblog/our-learnings-from-adopting-graphql-f099de39ae5f
總結(jié)
以上是生活随笔為你收集整理的Netflix:我们为什么要将GraphQL引入前端架构?\n的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 王者荣耀怎么投诉玩家
- 下一篇: 2019年的前端学习计划