[譯] 將現有的 API 從 REST 遷移到 GraphQL

zaraguo發表於2017-08-12

將現有的 API 從 REST 遷移到 GraphQL

最近的六個月內我發現幾乎每一場有關於 Web 開發的大會都談論到了 GraphQL。也有大量與其相關的文章被髮表。但是所有的這些幾乎都是在講 GraphQL 的基礎概念或者是新特性,說得很表面。因此我打算談談我在真實大型系統中採用 GraphQL 的個人經驗。

REST 有什麼問題

REST(一如 SOAP)沒有分離傳輸、安全和資料邏輯層面。這會帶來很多問題。讓我們來看看其中的幾個。

GET 查詢能力的低下

用 GET 語句進行復雜深入的查詢是不可能的。假設我們需要查詢使用者。舉一個很簡單的例子:

GET /users/?name=Homer

然後想象一下,我們需要查詢名字是 Homer 或者 Marge 的使用者。事情就變得有點棘手了。當然,我們可以為這種需求定義一些分隔符。

GET /users/?name=Homer|Marge

但是,不要忘記轉義這些字元!並且牢記,如果有人的名字中包含 “|” 那麼你就完蛋啦。如果要結合兩個不同的欄位那麼就更復雜了。更別說是需要同時滿足上面兩種條件的查詢。

目前我們一般都是使用欄位來查詢對應的內容。但是也時常需要用查詢語句來傳遞一些服務資料。比如頁碼:

GET /users/?name=Homer|Marge&limit=10&offset=20

按邏輯來說,我們後端的查詢解析器應該會將 limit 和 offset 識別為資料庫的欄位,因為他們被宣告為和 “name” 欄位同級的引數名。

我們可以發明我們自己的語法或是用 POST 方法(這是不對的,因為這是一個冪等請求)但是這看起來像是在造輪子。

資料更新的問題

使用 PUT 傳送整個物件是最簡單的 REST 更新資料的方式。但顯而易見的是,當你僅僅只需要更新 1 Mb 大小的物件中的一個欄位時,這並不是最有效的方式。

HTTP 還有一個 PATCH 方法。但是它有一個問題。用演算法來定義 如何更新實體 並不簡單。現有多個規範建議你應該如何去做,比如 RFC 6902,RFC 7396 以及許多自定義解決方案。

命名問題

我猜測每個曾與 REST 打交道的開發者都明白這種感受,當你不知道如何去命名你的新路由時。並非所有的業務例項都可以被描述為資源。例如我們想要搜尋帶有商店資訊的商品。

GET /search?product_name=IPhone&shop_name=IStore

這裡的資源是什麼?商品?商店?搜尋?

天哪,我的 API 不再是 REST 風格了!

另一個典型的例子便是使用者登入。這裡的資源又是什麼?Spoiler:這裡沒有資源,這裡只是個遠端過程呼叫而已。

後端處理 REST

app.post((req, res) => {
  const user = db.getUserByName(req.headers.name);
  const user = db.getUserByName(req.query.name);
  const user = db.getUserByName(req.path.name);
  const user = db.getUserByName(req.body.name);
});複製程式碼

這是一個 Express 路由的例子。這裡我們試圖獲取使用者的 ID 來查詢使用者。讓我們看一看 API 函式通常應該是什麼樣子:

函式接收引數,進行特定的處理並返回特定的結果。

在這個 Express 路由的例子中我們的引數是什麼?一個巨大雜亂的 req 物件,而我們僅需要其中很小的一部分資料。

當然,這也是 Express 的一個問題(準確的說是 Node 的 HTTP 模組的問題),但是這樣的介面也是因為 HTTP 的實現逐步進化而產生的 - 請求引數可以在任何位置,所以如果你本人不知道它或者沒有使用描述良好的文件時想要準確知道引數位置是不可能的。

這就是為什麼使用沒有介面文件的 REST 是如此的痛苦。

GraphQL

在這裡我們假設你早就熟悉 GraphQL 的基礎知識。如果沒有,你可以從 Apollo 寫的關於 GraphQL 基礎知識的介紹開始。

正如我們前面所展示的,REST 存在一些 GraphQL 所沒有的設計上的問題。並且 GraphQL 有著巨大的發展潛力。

首先 GraphQL 提供 RPC 訪問方式,這意味著你將不受客戶端-服務端的互動限制。GraphQL 有它自己的型別系統,這意味不再有令人誤解的錯誤和漏洞。並且型別系統意味著你的客戶端可以提供 item 級別的資料智慧快取。還擁有大量像是網路連線(遊標和分頁)、批處理、延時等的面向 Web 的特性。它 使你的客戶端-服務端互動儘可能的高效

但是 REST 仍然是業內標準

是的,無論我們是否喜歡,REST 都是近幾年 API 的主流形式。

但是我們仍然可以為一些內部需求(比如對接一些高階客戶端)去使用 GraphQL,其他的使用 REST。

為此,我們需要將 REST 路徑包裝成 GraphQL 型別。這裡有一些文章和例子(被提到最多的是 swapi-rest-graphql)關於從 REST 遷移到 GraphQL。但是它們建議使用自定義解析器,這無法滿足擁有成百上千路徑的大型專案。

在我最近的三個專案中我使用 Swagger 來描述 REST 介面。它或多或少都算是宣告式介面描述的標準。坦白說我真的不知道那些編寫龐大卻毫無描述的介面的人們是如何做到的。

一方面我們把 Swagger 作為宣告式 REST 介面的標準,另一方面也可以這麼看 GraphQL,我們可以看到它們其實非常相似,只是除此之外 Swagger 還嘗試去描述 HTTP 細節和業務邏輯。它們都描述了傳入引數和傳出響應的型別。這意味著我們可以在它們之間寫介面卡!

REST 路徑是這樣子的

GET /user/id

可以採用 GraphQL 型別。

所以現在我們只需一個庫來幫助我們自動轉換。下面這個就是!

github.com/yarax/swagg…

Swagger2graphQL 接收你的 Swagger schema 然後返回 GraphQL schema,同時解析程式將自動構建 HTTP 請求到已有的 REST 路徑上。

它被構建為一個將擁有超過 150 個路徑的真實大型系統遷移到 GraphQL 的副專案。我們需要在做功和問題都最少的情況下儘快地遷移到 GraphQL。

只需要克隆資源庫,執行

npm install && npm start

然後訪問 http://localhost:3009/graphql

你會看到封裝在 petstore.swagger.io/ Swagger 示例介面上的 GraphQL 介面。

而且,有了 Swagger 和 GraphQL 編寫新的路徑將變得十分方便。如果你早就熟悉 GraphQL,你可能會發現有時候型別描述看起來相當冗長,因為你需要去建立大量的隱式型別。Swagger2graphQL 可以自動完成這些步驟,你只需要在 Swagger schema 中建立一個新的帶有宣告的路徑,通常這很簡單。

如果你遇到任何困難或者有疑問請向我提 issue!

同時你也可以在 Twitter 上找到我


掘金翻譯計劃 是一個翻譯優質網際網路技術文章的社群,文章來源為 掘金 上的英文分享文章。內容覆蓋 AndroidiOSReact前端後端產品設計 等領域,想要檢視更多優質譯文請持續關注 掘金翻譯計劃

相關文章