<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          為什么我使用 GraphQL 而放棄 REST API?

          共 9532字,需瀏覽 20分鐘

           ·

          2021-05-22 00:40

          點(diǎn)擊上方 前端Q,關(guān)注公眾號

          回復(fù)加群,加入前端Q技術(shù)交流群




          作者 | Max Desiatov
          譯者 | 平川
          策劃 | 萬佳

          本文最初發(fā)布于 Max Desiatov 的個人博客,經(jīng)原作者授權(quán)由 InfoQ 中文站翻譯并分享。

          在大多數(shù)移動和 Web 應(yīng)用中,服務(wù)器交互需要花費(fèi)開發(fā)人員大量時間和精力來開發(fā)和測試。

          在我所開發(fā)的那些擁有最復(fù)雜 API 應(yīng)用程序中,網(wǎng)絡(luò)層設(shè)計和維護(hù)占去高達(dá) 40% 的開發(fā)時間,特別是由于我在本文中提到的一些邊緣情況。這樣實現(xiàn)過幾次后,很容易就會發(fā)現(xiàn),有一些不同的模式、工具和框架可以帶來幫助。雖然我們很幸運(yùn),不必再關(guān)心 SOAP,但 REST 也不是歷史的終結(jié)。

          最近,我有機(jī)會為自己的項目和客戶開發(fā)和運(yùn)行一些使用 GraphQL API 構(gòu)建的移動和 Web 應(yīng)用程序。這真是一個很好的體驗,尤其要感謝令人驚嘆的 PostGraphile 和 Apollo。至此,我再也無法回過頭來享受使用 REST 的工作了。

          REST 有什么問題嗎?
           每個 REST API 都是獨(dú)特的

          公平地說,REST 甚至不是一個標(biāo)準(zhǔn)。維基百科將其定義為:

          一種架構(gòu)風(fēng)格,基于 HTTP 定義了一組約束和屬性。

          雖然確實存在像 JSON API 規(guī)范這樣的東西,但在實踐中,我們很少看到有 RESTful 后端實現(xiàn)它。在最好的情況下,你可能會偶然發(fā)現(xiàn)一些使用 OpenAPI/Swagger 的東西。即使這樣,OpenAPI 也沒有指定 API 的形狀或格式,它只是一個機(jī)器可讀的規(guī)范,允許(但不是要求)你對 API 運(yùn)行自動化測試、自動生成文檔等。

          主要問題仍然存在。你可能會說你的 API 是 RESTful 的,但是對于如何安排端點(diǎn)或是否應(yīng)該(例如)使用 HTTP 方法PATCH進(jìn)行對象更新,一般沒有嚴(yán)格的規(guī)則。

          還有一些東西乍一看是 RESTful 的,但如果你仔細(xì)看,就不是那么像了:Dropbox HTTP API。

          端點(diǎn)接受請求體中的文件內(nèi)容,因此,它們的參數(shù)將以 JSON 的形式在Dropbox-API-Arg請求頭或 arg URL 參數(shù)中傳遞。

          JSON 在請求頭中?

          沒錯,Dropbox API 端點(diǎn)要求你將請求正文留空,并將有效載荷序列化為 JSON,放到一個自定義的 HTTP 頭中。為這種特殊情況編寫客戶端代碼很有趣。我們不能抱怨,因為畢竟沒有廣泛使用的標(biāo)準(zhǔn)。

          事實上,下面提到的大多數(shù)注意事項都是由于缺乏標(biāo)準(zhǔn)造成的,但是我想強(qiáng)調(diào)一下在實踐中經(jīng)常看到的情況。

          在一個有經(jīng)驗的團(tuán)隊中,你可以避免這些問題,但是你難道不希望一些問題已經(jīng)在軟件方面得到解決嗎?

           沒有靜態(tài)類型意味著要注意類型驗證

          無論如何努力避免這種情況,你遲早會遇到 JSON 屬性拼寫錯誤、發(fā)送或接收的數(shù)據(jù)類型錯誤、字段丟失等問題。如果你的客戶端和 / 或服務(wù)器編程語言是靜態(tài)類型的,并且你不能用錯誤的字段名或類型構(gòu)造對象,那可能沒問題。如果你的 API 是版本化的,舊 API 的 URL 為/API/v1,新版本的 URL 為/API/v2,那么你可能做得很好。如果有一個 OpenAPI 規(guī)范,可以為你生成客戶端 / 服務(wù)器類型聲明,那就更好了。

          但你真能負(fù)擔(dān)得起在所有項目中都做到這樣嗎?當(dāng)你的團(tuán)隊在沖刺期間決定重命名或重新安排對象字段時,你能負(fù)擔(dān)得起上線/api/v1.99端點(diǎn)的成本嗎?即使完成了,團(tuán)隊會不會忘記更新規(guī)范并通知客戶端開發(fā)人員更新內(nèi)容?

          在客戶端或服務(wù)器上的所有驗證邏輯,你確定都是正確的嗎?理想情況下,你希望它在兩邊都得到驗證,對吧?維護(hù)所有這些自定義代碼非常有趣。或者保持 API JSON 模式是最新的。

           分頁和過濾并不簡單

          大多數(shù) API 都使用對象集合。在待辦事項列表應(yīng)用中,列表本身就是一個集合。大多數(shù)集合都可以包含 100 多個項。對于大多數(shù)服務(wù)器來說,在一次響應(yīng)的一個集合中返回所有項是一個繁重的操作。如果再乘以在線用戶的數(shù)量,就會產(chǎn)生很大的 AWS 賬單。顯而易見的解決方案:只返回集合的子集。

          分頁相對簡單。在查詢參數(shù)中傳遞類似offsetlimit這樣的值:/todos?Limit =10&offset=20以獲得從 20 開始的 10 個對象。每個人對這些參數(shù)的命名都不一樣,有些人喜歡countskip,而我喜歡offsetlimit,因為它們直接對應(yīng)于 SQL 修飾符。

          一些后端數(shù)據(jù)庫會暴露要傳遞給下一頁查詢的游標(biāo)或標(biāo)記。請查看 Elasticsearch API,該 API 建議在需要依次瀏覽大量結(jié)果文檔時使用scroll調(diào)用。還有一些 API 在頭中傳遞相關(guān)信息。參見 GitHub REST API(至少不是在頭中傳遞 JSON)。

          說到過濾,就有趣多了……需要按一個字段過濾嗎?沒問題,可能是/todos?filter=key%3Dvalue,也可能是可讀性更好的/todos?filterKey=key&filterValue=value。那么按兩個值過濾呢?這應(yīng)該很簡單,對吧?使用 URL 編碼,查詢看起來是這個樣子:/todos?filterKeys=key1%2Ckey2&filterValue=value。但通常,我們沒有辦法阻止特性蔓延,可能會出現(xiàn)使用AND/OR操作符進(jìn)行高級過濾的需求。或者復(fù)雜的全文搜索查詢和復(fù)雜的過濾。遲早你會看到一些 API 發(fā)明了自己的過濾 DSL。URL 查詢組件已經(jīng)不夠用了,但是GET請求中的請求體也不太好,這意味著你最終要在POST請求中發(fā)送非可變查詢(Elasticsearch 就是這樣做的)。至此,API 還是 RESTful 的嗎?

          無論哪種方式,客戶端和服務(wù)器都需要特別注意解析、格式化和驗證所有這些參數(shù)。如此多的樂趣!舉例來說,如果沒有恰當(dāng)?shù)尿炞C且存在未初始化的變量,你就很容易地得到類似這樣的東西:/todos?offset=undefined

           不容易記錄和測試

          上面提到的 Swagger 可能是目前最好的工具,但其應(yīng)用還不夠廣泛。根據(jù)我的觀察,更常見的情況是,API 文檔單獨(dú)維護(hù)。對一個穩(wěn)定且廣泛使用的 API 來說,這沒什么大不了的,但是在敏捷流程的開發(fā)過程中,這就比較糟糕了。文檔單獨(dú)存儲意味著,它經(jīng)常不會更新,特別是當(dāng)更改是一個小的、但會破壞客戶端的更改時。

          如果你不使用 Swagger,這可能意味著你需要維護(hù)專門的測試基礎(chǔ)設(shè)施。與單元測試相比,你對集成測試(即同時測試客戶端和服務(wù)器端代碼)的需求會更多。

           關(guān)系查詢和批量查詢會讓人更加沮喪

          對于比較大的 API,這就成了一個問題,因為你可能有許多相關(guān)的集合。讓我們進(jìn)一步來看一個待辦事項列表應(yīng)用程序的例子:假設(shè)每個待辦事項也可以屬于一個項目。你是否總是希望一次獲取所有相關(guān)的項目?可能不需要,但是還需要添加更多的查詢參數(shù)。也許你不想一次獲取所有對象字段。如果應(yīng)用程序需要項目有所有者,并且除了每個集合有單獨(dú)的視圖顯示外,還有一個視圖顯示所有這些數(shù)據(jù)的聚合?它要么是三個獨(dú)立的 HTTP 請求,要么是一個復(fù)雜的請求,同時獲取所有數(shù)據(jù)用于聚合。

          無論哪種方式,都存在復(fù)雜性和性能上的權(quán)衡,在不斷發(fā)展的應(yīng)用程序中維護(hù)這些請求會帶來更多令人頭痛的問題。

           你需要同時在服務(wù)器和客戶端上實現(xiàn)每個端點(diǎn)

          還有大量的庫可以在 ORM 或直接數(shù)據(jù)庫自省的幫助下自動生成 REST 端點(diǎn)。即使使用了這樣的庫,它們通常也不是很靈活或可擴(kuò)展的。也就是說,如果需要自定義參數(shù)、高級過濾行為或?qū)φ埱?/ 響應(yīng)有效負(fù)載的一些更智能的處理,就需要從頭重新實現(xiàn)端點(diǎn)。

          另一項任務(wù)是在客戶端代碼中使用這些端點(diǎn)。如果有的話,最好使用代碼生成,但是它似乎不夠靈活。即使是使用像 Moya 這樣的輔助庫,也會遇到同樣障礙:有許多自定義行為需要處理,這是由前面提到的邊緣情況引起的。

          如果開發(fā)團(tuán)隊不是全棧的,那么服務(wù)器和客戶端團(tuán)隊之間的溝通就至關(guān)重要,在沒有機(jī)器可讀的 API 規(guī)范的情況下更是如此。

          GraphQL 如何做得更好?

          對于所有討論過的問題,我傾向于認(rèn)為,在 CRUD 應(yīng)用程序中,有一種標(biāo)準(zhǔn)方式來生成和使用 API 會非常棒。通用的工具和模式、集成測試和文檔基礎(chǔ)設(shè)施將有助于解決技術(shù)和組織問題。

          GraphQL 有一個 RFC 規(guī)范草案 和一個參考實現(xiàn)。此外,請參閱 GraphQL 教程,它描述了你需要了解的大多數(shù)概念。有針對不同平臺的實現(xiàn),也有許多可用的開發(fā)工具,其中最著名的是 GraphiQL,它捆綁了一個很好的、具有自動完成功能的 API 瀏覽器,以及一個文檔瀏覽器,可以瀏覽從 GraphQL 模式自動生成的文檔。

          事實上,我發(fā)現(xiàn) GraphiQL 是不可或缺的。它可以幫助解決我前面提到的客戶端和服務(wù)器團(tuán)隊之間的溝通問題。只要 GraphQL 模式中有任何更改,你就可以在 GraphQL 瀏覽器中看到它,就像嵌入式 API 文檔。現(xiàn)在,客戶端和服務(wù)器團(tuán)隊可以以一種更好的方式在 API 設(shè)計上開展合作,縮短迭代時間,共享自動生成的文檔,它們讓每次 API 更新對每個人都可見。要了解這些工具是如何工作的,請查看 Star Wars API 示例,它可以作為 GraphiQL 的在線演示。

          能指定從服務(wù)器請求的對象字段讓客戶端可以根據(jù)需要只獲取需要的數(shù)據(jù)。不再有多個重量級的查詢發(fā)送到一個剛性的 REST API,為了讓客戶端可以在應(yīng)用程序 UI 中一次性顯示它。你不再受限于一組端點(diǎn),而是有一個可以查詢和修改的模式,能夠挑選客戶端指定的字段和對象。服務(wù)器只需以這種方式實現(xiàn)頂級模式對象。

           一個簡單的例子

          GraphQL 模式定義了可用于在服務(wù)器和客戶端之間通信的類型。有兩種特殊類型,它們同時也是 GraphQL 的核心概念:QueryMutation。在大多數(shù)情況下,向 GraphQL API 發(fā)出的每個請求要么是沒有副作用的Query實例,要么是會修改存儲在服務(wù)器上的對象的Mutation實例。

          現(xiàn)在,繼續(xù)我們待辦事項列表應(yīng)用程序的例子,考慮下面這個 GraphQL 模式:
          type Project {
            id: ID
            name: String!
          }
          type TodoItem {
            id: ID
            description: String!
            isCompleted: Boolean!
            dueDate: Date
            project: Project
          }
          type TodoList {
            totalCount: Int!
            items: [TodoItem]!
          }
          type Query {
            allTodos(limit: Int, offset: Int): TodoList!
            todoByID(id: ID!): TodoItem
          }
          type Mutation {
            createTodo(item: TodoItem!): TodoItem
            deleteTodo(id: ID!): TodoItem
            updateTodo(id: ID!, newItem: TodoItem!): TodoItem
          }
          schema {
            query: Query
            mutation: Mutation
          }
          底部的schema塊是特定的,定義了前面描述的根類型QueryMutation。此外,它非常簡單:type塊定義新的類型,每個塊包含具有自己類型的字段定義。類型可以是非可選的,例如String!字段不能有空值,而String可以。字段也可以有命名參數(shù),所以TodoList!類型的字段allTodos(limit: Int, offset: Int): TodoList!接受兩個可選參數(shù),而其本身的值是非可選的,這意味著它將始終返回一個不能為空的TodoList實例。然后,要查詢所有待辦事項的id和名稱,你可以編寫這樣一個查詢:
          query {
            allTodos(limit: 5) {
              totalCount
              items {
                id
                description
                isCompleted
              }
            }
          }
          GraphQL 客戶端庫根據(jù)模式自動解析和驗證查詢,然后將其發(fā)送到 GraphQL 服務(wù)器。請注意,allTodos字段的offset參數(shù)是缺失的。作為可選項,它的缺失意味著它有null值。如果服務(wù)器提供這種模式,文檔中可能會聲明,null偏移量意味著默認(rèn)情況下應(yīng)該返回第一頁。響應(yīng)可能是這樣的:
          {
            "data": {
              "allTodos": {
                "totalCount": 42,
                "items": [
                  {
                    "id": 1,
                    "description": "write a blogpost",
                    "isCompleted": true
                  },
                  {
                    "id": 2,
                    "description": "edit until looks good",
                    "isCompleted": true
                  },
                  {
                    "id": 2,
                    "description": "proofread",
                    "isCompleted": false
                  },
                  {
                    "id": 4,
                    "description": "publish on the website",
                    "isCompleted": false
                  },
                  {
                    "id": 5,
                    "description": "share",
                    "isCompleted": false
                  }
                ]
              }
            }
          }

          如果你從查詢中刪除isCompleted字段,它將從結(jié)果中消失。或者你可以添加project字段,用其idname來遍歷關(guān)系。將offset參數(shù)添加到allTodos字段進(jìn)行分頁,這樣allTodos(count: 5, offset: 5)將返回第二頁。結(jié)果中提供了totalCount字段,這很有用,因為現(xiàn)在你知道總共有42 / 5 = 9頁。但顯然,如果不需要totalCount,你可以忽略它。查詢可以完全控制將要接收的實際信息,但是底層的 GraphQL 基礎(chǔ)設(shè)施還必須確保所有必需的字段和參數(shù)都在那里。如果你的 GraphQL 服務(wù)器足夠聰明,它將不會對你不需要的字段運(yùn)行數(shù)據(jù)庫查詢,而且有些庫好到免費(fèi)提供這種查詢。此模式中的其他變體和查詢也是如此:對輸入進(jìn)行類型檢查和驗證,并且基于查詢,GraphQL 服務(wù)器知道期望的結(jié)果形狀。本質(zhì)上,所有通信都通過服務(wù)器上一個預(yù)定義的 URL(通常是/graphql)運(yùn)行,借助一個簡單的POST請求,其中包含序列化為 JSON 有效負(fù)載的查詢。但是,你幾乎從來都不需要接觸如此低的抽象層。

          總體來說還不錯:我們已經(jīng)解決了類型級別的驗證問題,分頁看起來也不錯,并且在需要時可以輕松地遍歷實體關(guān)系。如果使用一些現(xiàn)成的 GraphQL->數(shù)據(jù)庫查詢翻譯庫,你甚至不需要在服務(wù)器上編寫大多數(shù)數(shù)據(jù)庫查詢。客戶端庫可以很容易地將 GraphQL 響應(yīng)自動解包為所需類型的對象實例,因為從模式和查詢可以提前知道響應(yīng)形狀。

           GraphQL 是個時髦的東西,是一種時尚,對嗎?

          雖然 Netflix falcor 似乎在解決類似問題,它比 GraphQL 早幾個月發(fā)布在 GitHub 上,也更早地引起我的注意,但很明顯,似乎 GraphQL 贏了。良好的工具和強(qiáng)大的行業(yè)支持使其非常有吸引力。

          除了一些客戶端庫中存在的一些小問題(現(xiàn)在已經(jīng)解決了)之外,我強(qiáng)烈推薦你仔細(xì)看看 GraphQL 在你的技術(shù)棧中可以提供什么。它已經(jīng)出技術(shù)預(yù)覽四年多了,而且這個生態(tài)系統(tǒng)正在變得更加強(qiáng)大。在 Facebook 設(shè)計 GraphQL 的同時,我們也看到越來越多的大公司在他們的產(chǎn)品中使用它:GitHub、Shopify、Khan Academy、Coursera,而且 這個列表還在不斷增長。

          有很多流行的開源項目都在使用 GraphQL:這個博客是基于靜態(tài)站點(diǎn)生成器 Gatsby,它將 GraphQL 查詢的結(jié)果轉(zhuǎn)換成數(shù)據(jù),然后呈現(xiàn)到 HTML 文件中。如果你使用的是 WordPress,也有 GraphQL API 可以使用。Reaction Commerce 是 Shopify 的開源替代方案,同樣是基于 GraphQL。

          另外值得一提的兩個 GraphQL 庫是 PostGraphile 和 Apollo。

          如果你使用 PostgreSQL 作為后端數(shù)據(jù)庫,PostGraphile 能夠掃描 SQL 模式并自動生成一個帶有實現(xiàn)的 GraphQL 模式。你可以將所有常見的 CRUD 操作暴露為所有表的查詢和修改。它可能看起來像 ORM,但它不是:你可以完全控制如何設(shè)計數(shù)據(jù)庫模式,以及使用什么索引。

          最妙的是,PostGraphile 還以查詢和修改的方式暴露視圖和函數(shù),所以如果有特別復(fù)雜的 SQL 查詢需要映射到 GraphQL 字段,只需創(chuàng)建 SQL 視圖或函數(shù),它就會自動出現(xiàn)在 GraphQL 模式中。通過像行級安全這樣的高級 Postgres 特性,你可以通過編寫少量 SQL 策略實現(xiàn)復(fù)雜的訪問控制邏輯。PostGraphile 甚至還有模式文檔這樣的東西,可以從 Postgres 注釋自動生成。

          相應(yīng)地,Apollo 提供了多個平臺的客戶端庫,以及在最流行的編程語言(包括 TypeScript 和 Swift)中生成類型定義的代碼生成器。

          總的來說,我發(fā)現(xiàn),Apollo 比 Relay 等更簡單和易于使用。由于 Apollo 客戶端庫架構(gòu)簡單,我能夠?qū)⒁粋€使用 React.js 與 Redux 的應(yīng)用慢慢過渡到 React Apollo,一個組件一個組件的,只在有意義的時候才這樣做。與原生 iOS 應(yīng)用一樣,Apollo iOS 是一個相對輕量級的、易于使用的庫。

           延伸閱讀

          https://desiatov.com/why-graphql/?fileGuid=cGOKAr3CJtY4Y9Rh



          內(nèi)推社群


          我組建了一個氛圍特別好的騰訊內(nèi)推社群,如果你對加入騰訊感興趣的話(后續(xù)有計劃也可以),我們可以一起進(jìn)行面試相關(guān)的答疑、聊聊面試的故事、并且在你準(zhǔn)備好的時候隨時幫你內(nèi)推。下方加 winty 好友回復(fù)「面試」即可。


          瀏覽 41
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  无码成人在线 | 日韩AV无码一区二区 | 亚洲图片欧美色图 | 欧美日韩成人视频 | 国产做受 高潮豆麻 |