GraphQL 的解題技巧:概念與使用指南
在這篇文章中,我探討了 GraphQL 的出現背景和它如何解決 RESTful API 的一些限制,例如過度擷取和多次請求問題。我解釋了 GraphQL 允許前端更精確地指定所需數據的能力,從而減少不必要的數據傳輸,並提高應用效率。此外,我還比較了 GraphQL 和 RESTful API 的主要差異,強調了 GraphQL 在資料查詢靈活性上的優勢。
一、GraphQL 是要解決甚麼問題?
GraphQL (Graph query language) 是一個由 Facebook 開發、而且公開的為 API 設計的資料查詢語言,在2012年出現在2015公開釋出。
大概念上就是讓前端可以比較靈活且精準的透過 API 取得資料,可以在某些場景下解決 RESTful API 一些讓人困擾的問題。
- RESTful API 回傳的資料一大包中很多都是不需要的,但不同應用端上需要不同的資料
- 前端需要修改 API 取得的資料的需求十分麻煩,小變動也需要卡到後端
GraphQL 設計的大概念上有點類似 SQL,GraphQL使用查詢(Query)字段去請求數據而非 SELECT。
前端則可以用 GraphQL 這套查詢語言來向 Server 查詢想要的資料,GraphQL 具有訂閱功能,可以用來監聽Socket 連接上的數據變動。
※ 註:
- 符合 REST (Representational State Transfer) 原則的系統就是 RESTful (或是 REST-compliant )
- 舉例:HTTP 就是一個符合 REST 架構的實作,但 REST 是一種風格 (並非標準),所以應用程式使用了 HTTP 連線不保證它就是 REST 架構
- RESTful API 是符合 REST 原則的 API 架構,通過 REST 的規範幫助制定出漂亮的 API
二、GraphQL 跟 RESTful API 差別
RESTful API
- 優點
- 前後端高度解耦便於理解,即使沒有看文檔,也能大概知道接口是用來做什麼的
- 接口的功能有單一性,便於擴展和復用
- 利用了 HTTP 原本的特性 (CRUD)
- 缺點
- 有時 payload 會變的特別大
- 同一個頁面可能要調用很多個 API,來獲取不同的東西,在網絡差的情況下會降低體驗
GraphQL
- 優點
- 精準資料取得
- 前後端溝通成本減少,前端控制權提升
- 程式即文檔
- 高度自由的實作方式
- 可將不同 micro service 的 GraphQL schema 串接在一起
- 強型別,型別錯就直接被擋下來,支援五種基礎型別 (Scalar Types)
- 缺點
- 沒有一定的實作規範,可能不熟悉以致設計出複雜的 Schema
- 很容易一不小心陷入 RESTful API 的設計思維
- Server Side Caching 實作困難
圖片來源: 2019-GraphQL-簡介
1. RESTful API
RESTful API 的溝通方式,是針對需要的功能定義好不同的 API,每一個 API 都有自己的參數跟回傳格式等,client 需要了解某個 API 所需要、和能提供的資料做對應的處理。
假設我們想要取得 SpaceX 裡的火箭列表,我們可能就會針對某個 API endpoint 下一個 GET request,去取得一個包含火箭資料的 JSON。
curl -s https://api.spacexdata.com/v3/launches/92
會得到一個包山包海的 JSON response:
{
"flight_number": 92,
"mission_name": "Starlink 5",
"rocket": {
"rocket_id": "falcon9",
"rocket_name": "Falcon 9",
…
},
… // 資料太............多所以省略
"last_wiki_revision": "c924fae3-67b8-11ea-b7a8-0e7927e3be09",
"last_wiki_update": "2020-03-16T19:03:14.000Z",
"launch_date_source": "launch_library"
}
2. GraphQL
GraphQL 只定義了 server 上有的資料類型,但不會有明確的 API 來限制傳入的參數、跟取出的資料格式。Client 在取資料的時候,只需要跟 server 指定想要取的資料格式、想要的欄位等等,server 就會根據 client 指定的格式回傳對應的資料。
假設在 server 上我們有這樣的資料:
type Launch {
id: ID!
site: String
mission: Mission
rocket: Rocket
isBooked: Boolean!
}
如果我們想要取得某筆試射資料,我們會用這樣的方式跟 server 要資料:
query GetLaunch {
launch(id: "92") {
id
site
}
}
而 server 就會乖乖地給我們回傳一個 JSON:
{
"launch": {
"id": "92",
"site": "KSC LC 39A"
}
}
可以被 GraphQL 查詢的資料,本身就是一張 graph
圖片來源: GraphQL 初初探
而每次查詢,就是在取得這張 graph 的其中一部分,這就是 GraphQL 的名稱由來 the Graph Query Language
圖片來源: GraphQL 初初探
三、GraphQL 基本概念跟語法
GraphQL 並不是一個後端或前端的 framework,而是一套規範好的語法介面,讓前後端可以根據這套規範來溝通。
就好像我們會用 RESTful 當成 API 的設計準則,我們也可以使用 GraphQL 來取代 RESTful,當成是我們 API 的設計準則,定好這樣的準則之後,前後端會各自針對這個規範,實做出能夠符合這樣規範的程式。
可使用類似 Postman 功能的 GraphQL Playground (可用 npm 安裝本地版本)
1. Schema
GraphQL 透過一個特殊的語法格式,來定義 server 能提供的資料型態,這樣的格式被稱作為 schema。
透過 schema,我們可以了解到 server 提供了哪一些資料型態讓我們使用,client 也就可以針對這些資料格式取用所需要的資料。
type Launch {
id: ID!
site: String
mission: Mission
rocket: Rocket
isBooked: Boolean!
}
這個 Launch 就是我們定義好的資料型態,而 String、Int 也是 GraphQL 上的資料型態
- GraphQL 上面有兩種基本的資料型態類型 (type),分別是 Object type 跟 Scalar type
- Object 就是任何自定義的物件,像是 Launch 就是其中一種
- Scalar 就是一些很基本的資料型別,像是 String、Int 等等
在上面這個 Launch 類別裡的 id、site 等等,我們稱它們為 field(欄位)
- 欄位的後面就是這個欄位的類別,它可以是一個 Object(像是 mission),也可以是一個 Scalar(像是 site)
- field 裡面有一個特別的類別 ID,ID 也是一種 Scalar,底層其實就是一個字串,只不過 ID 確保了同一個階層裡這個攔位會是唯一的
- ID 後面,還有一個特別的驚嘆號 (!)。代表的是這個欄位是一定要有值的 (non-nullable)。
圖片來源: 2019-GraphQL-簡介
※ 註: 當在關聯式資料庫 (如 PostgreSQL) 中實現邏輯時,Hasura GraphQL Engine 能夠通過 GraphQL 查詢和突變將它們公開給前端應用程序。
2. Query
在 GraphQL 當中,可以分為兩種操作:query 與 mutation
- query 指的是查詢資料
- mutation 則是任何會對資料做更動(增、刪、改)
雖然 resolver 在實作上可以自己定義,如果你想要用 query 來完成所有事情也不是不可能,但實務上通常會根據是否有更動資料來選擇要用 query 還是 mutation。
client 可以透過下達 query 指令,跟 server 要資料或者寫入資料,針對上面 Launch 的這個 schema,我們下達的 query 可以是這樣:
query GetLaunch {
launch(id: "92") {
site
}
}
這段 code 代表的,是跟 server 拿一筆火箭發射的資料
- 指定 id 需要是 “92”,而且回傳值只需要包含 site 這個 field
- 這樣就算是一個操作 (operation)
- 前面的 query GetLaunch 當中,“GetLaunch” 是可以自由指定的名稱 (operation name)
- 而 “query” 就是這次操作的種類 (operation type)
- 在 GraphQL,總共有三種 operation type
- query: 單純的查詢資料
- mutation: 寫入資料到 server
- subscription: 查詢資料,並且在接下來的資料更新時收到新資料
Operation type 跟 name 底下的第一層,一般稱作 Root type 或是 Query type
- 在 schema 裡面通常會被寫成 type Query 或 type Root
- 這個可以看做是 GraphQL 的 entry point,每一個 query 都會以 Root type 作為最上面的一層,每一個 query 都需要從 root type 開始
- 在查詢 GraphQL schema 文件的時候,我們可以先從這個 type 開始看起,了解有哪一些 entry points 是可以被呼叫的
GraphQL 會將查詢的語法透過解析器轉換成語法樹,再遞迴呼叫每個欄位對應的 resolver
圖片來源: 深入現代前端開發/GraphQL
因為有解析這個步驟,所以儘管只是純字串,如果語法有錯誤還是可以直接偵測錯誤。另外因為 GraphQL 內建的強型別系統,每個欄位都需要定義型別,近一步讓欄位的存取更加安全。
四、GraphQL 建置
主要的 GraphQL 客戶端有 Apollo Client 和 **Relay**,後者是官方推出的較為複雜,前者則是社群驅動的,使用簡易且社群資源與開源 package 豐富。簡單來說 Apollo Client 就是一個幫你接收 GraphQL requests 然後更新客戶端的工具。
在 Apollo Client 2.6 已經推出了用 hooks 為主流的開發方式,在 2.5 之前,仍然是使用 component 搭配 render props 的方式為主。
在 react 專案中安裝
npm install apollo-boost @apollo/react-hooks graphql
- index.js 設定
import ApolloClient from "apollo-boost"
const client = new ApolloClient({
uri: "https://..."
})
- 在最上層的 component 做一個 Provider,將狀態利用 context 或 HOC 的方式傳入
import React from "react"
import { render } from "react-dom"
import { ApolloProvider } from "@apollo/react-hooks"
import ApolloClient from "apollo-boost"
const client = new ApolloClient({
uri: "https://..."
})
const App = () => (
<ApolloProvider client={client}>
<div>
<h2>My first Apollo app 🚀</h2>
</div>
</ApolloProvider>
)
render(<App />, document.getElementById("root"))
後續在元件內取得 GraphQL 資料和相關設定請見 Apollo Client 攜手 React 擁抱 GraphQL & 使用 React + Apollo Client 設計一個部落格系統 文章。
參考資料2019-GraphQL-簡介GraphQL 教學:為你迭代快速的專案 提供最適合的解決方案!深入現代前端開發/GraphQL簡單理解REST設計風格與RESTful APIRPC 、REST、GraphQL三种API设计方式的简介和比较GraphQL 初初探GraphQL 初初探:Relay,把資料接到介面上!GraphQL讓前後端API說同樣的語言GraphQL 是 API 的未来,但它并非银弹Think in GraphQLGraphQL和REST有什麼不同?GraphQL 在前端的应用在 React 專案裡加入 Apollo Client通過前端工程化將 Apollo 引入現有 React 技術棧前端 Apollo Client 串接 GraphQL APIGraphQL工具包Apollo的完整介紹透過 apollo-server 在 10 分鐘內打造你的第一個 GraphQL serverGraphQL Playground 完整指南使用 Hasura 和 PostgreSQL 构建后端技巧你会吗Hasura GraphQL引擎爲什麼我勸你放棄了Restful API?