GraphQL in Ruby on Rails

來看看 GraphQL 與它在 Rails 中的實作

GraphQL is a query language for your API, and a server-side runtime for executing queries by using a type system you define for your data.

(節錄自 GraphQL)。

GraphQL 是一種新型態的 API(Application Programming Interface) 查詢方式,是 Facebook 在 2012 年開始使用,2015 年推出的。時至今日,已經有越來越多厲害公司使用它,是個當紅寵兒來著。那在 GraphQL 出現以前大家都是怎麼度過每一天的呢?GraqhQL 到底改變了什麼?

GraphQL 顛覆/改進的是 REST(REsourceful State Transfer)架構。REST 是以往主流的資料傳輸規範。其實 GraphQL 也算是基於 REST 架構改良而成,所以如果要暸解 GraphQL,先把 REST 弄懂絕對值回票價!而暸解了 REST 之後就更能夠體會 GraphQL 之所以強大的原因了。

由於 GraphQL 的服務可以被任意語言使用,這篇文章的例子會用 Ruby 來實作,並且介紹 GraphQL 在 Ruby on Rails 中的應用。

我們先直接從例子來看看 GraphQL 與 REST 這兩者的不同。

Resources

Resource 是 REST 中很重要的觀念。每一個 Resource 都會有個 Identifier (通常就是 URL),我們利用這個 Identifier 加上一個動作(比如說 GET 或是 POST)來操作 Resource。

假設今天我們想要拿到一個 user 的資料,使用 REST 可能會是以下的情況:

# REST QUERY
GET /user/1 # retrieve user data where user id is 1
# REST RESPONSE (IN JSON)
{
"username": "jennycodes",
"website": {
"url": "https://jennycodes.me",
"quality": "quite high"
}
"phone": "..."
...(omitted)
}

當我們呼叫了 GET /user/1,我們得到一組預先被定義好的資料,通常會稱呼它 the user endpoint。用去餐廳吃飯做比較,REST 就是提供套餐(事先規劃好的 API endpoint),顧客只能選擇要哪個套餐(我要第幾個 User 的資料),並且一次只能選擇一個,想要一次點兩種套餐(一次 query 中請求多個資源)?不行。而且選好之後你就是拿到一組固定的東西,如果我今天只想吃單純的一盤蛋炒飯,我只能點蛋炒飯套餐再自己把小菜跟飲料去掉。今天我想吃蛋炒飯+紅蘿蔔蛋炒飯,我只能分兩次點,而且還是只能點套餐。今天我只想知道 User 1 的 username 跟 website quality,我就要自己從拿回來的這一包 response 自己去把資訊挑出來。

如果是 GraphQL,則會長這樣:

# GraphQL QUERY
GET /graphql?query={ user(id: "1") { username, website { quality } } }
# GraphQL RESPONSE 
{
"username": "jennycodes",
"website": {
"quality": "quite high",
}
}

差別在哪?我在 query 裡面指明了我想要哪些資料(User 1 的 username 與 website quality),而 GraphQL 回傳給我的也就只包含了那些資料。前一個例子中的 response 雖然也給了我這些資料,但是它還包含了其他我不想知道的訊息,讓我還要對拿回來的東西自己加工處理,這個例子中的 response 就都是我想要的。回到餐廳的比喻,GraphQL 是提供單點,顧客可以自由搭配。今天我只想吃蛋炒飯,我就只點蛋炒飯;今天我想吃一盤蛋炒飯加上一盤紅蘿蔔蛋炒飯,也可以直接在同一個 query 裡面點完,想要什麼點什麼。酷吧。

GraphQL 三要素

從上面的簡單小例子應該可以稍微領略到 GraqhQL 的威力了。用一個直觀又彈性的查詢就可以得到我們想要的回應,溝通從來沒有如此輕鬆無障礙💪🏿 所以 GraqhQL 到底是怎麼做到的呢?我們先來認識 GraqhQL 的三個基本要素:Schema, TypeQuery

Schema

GraphQL query language is basically about selecting fields on objects.

節錄自 GraphQL

使用 GraphQL 其實就是找到物件(object),從物件中選取屬性(field)的過程。要如何知道哪些屬性是我們可以選的?回傳值的型態會是什麼?那些 sub-object (website)又有哪些欄位可以選擇?這就是 Schema 登場的時候了。

Schema 其實跟資料庫的 Schema 是相同的概念,它定義了 GraphQL query 基本的行為與架構,還有設定。以下是一個最基本的 Schema(使用 Ruby, as promised)。

# app/graphql/graphql_schema.rb
class Schema < GraphQL::Schema
query QueryType
end

這邊單純是將 query(進入 GraphQL 的起手式,下面說明)指向 QueryType 這個類別。如果想要看更詳細的模板請繼續看下去。

Type

[Object types] just represent a kind of object you can fetch from your service, and what fields it has.

節錄自 GraphQL

GraphQL 中的 Type 其實就像是資料庫裡面的資料表(data table)。在 Ruby 中它大概長這樣:

# app/graphql/types/user_type.rb
class Types::UserType < Types::BaseObject
description "A User"
field :username, String
field :phone, String
field :website, Types::WebsiteType
end
# app/graphql/types/user_type.rb
class Types::WebsiteType < Types::BaseObject
description "Website of various qualities"
field :url, String, null: false
field :quality, String
end

是不是跟 Rails ActiveRecord::Schema 的資料表 syntax 超級像。

Query

大多數在 Schema 中的 Type 都是一般的 Object Type,但是有一種 Type 地位與眾不同:Root Type。Root Type 是 GraphQL query 的進入點(entry point),分成三種:Query, Mutation, Subscription。其中 Query 是必須要有的,其它兩種是自由選擇。假設我們看到客戶端發送這個請求

GET /graphql?query={ user(id: "1") { username, website { quality } } }

如果這個請求是合法的,這表示在伺服器端一定會有類似的宣告:

# app/graphql/types/query_type.rb

class QueryType < GraphQL::Schema::Object
description "The query root of this schema"
  field :user, UserType, null: true do 
description "Find a user by ID"
argument :id, ID, required: true
end
  # implementation
def user(id:)
Post.find(id)
end
end

在 QueryType 類別中我們定義一個名為 user 的進入點,它會去找 UserType,把適當的參數傳進去(id: 1),然後拿回資料。

所以說,在 GraphQL 中,Query 就是那本菜單,讓客戶知道可以怎麼點菜(怎麼查詢),還有拿回來的菜色是什麼(回傳值長什麼樣子)。

GraphQL in Rails

基本上,現在 GraphQL 跟很多語言都整合的很好了。Rails 中也可以直接使用 graphql-ruby gem 來實作。像是以下 generator

$ rails generate graphql:install

會做到:

  • app/graphql/建立相關資料夾。
  • 增加 schema definition。
  • 增加 base type classes。
  • 加上一個Query type definition。
  • 增加 route 和 controller 來執行 queries。
  • 安裝 graphiql-rails

就跟 scaffold 一樣幫你省下一堆功夫,同時也讓你能夠快速上手使用它。很建議可以自己 rails new 一個新專案來試試 GraphQL,邊做邊學,會比較有感。而且這些 generator 都幫你做好了,不用白不用。

必須要裝的 GraphiQL

有沒有看到剛剛使用 $ rails generate graphql:install自動產生的功能最後一條是「安裝 graphiql-rails」?除了自己開一個專案來實際玩玩之外,一定要做的事情就是去裝 GraphiQL。詳細到底有多好用就等你自己去發掘了!

比較詳細的 Schema 樣板

剛剛例子裡的 schema 是最簡單的那種,這邊提供一個詳細一點的模板讓有心人參考。

https://gist.github.com/jenny-codes/0e340f08454ec233bb9a2411d7c44e35

我們來一個個研究。

Default Limits

每個 field 都有一個 complexity value 我們可以定義。然後在 Schema 我們定義 max_complexity 來預防太複雜的 query。

而 max_depth 則是防止太多層的 nested_query。因為 GraphQL 的 query 是客戶端可以自己決定的,當結構變得複雜可能會出現一個包了很多層的 query(e.g. 用戶一號的網站的讀者的朋友的手機的電信公司的董事長的情人的眼鏡的價錢)。看得出來,定義一個適當的最高層數限制是好的。

Root Type

前面介紹了 Root Type 中的 Query,而 Mutation 與 Subscription 又是什麼呢?基本上,Query 用來查詢資料,Mutation 用來操作(新增、修改等等),而 Subscription 則是拿來實現聊天室這種即時請求用的。

Plugins

我們使用 use 關鍵字添加 plugins 為 schema 增加新的行為(而省掉不必要的 api 負擔),比如說整合 NewRelic 可以用use(GraphQL::Tracing::NewRelicTracing)

Introspection

GraphQL schema 有一個內建的 Introspection System,讓我們知道這個 schema 的架構。

先一個暫時的結尾

其實這篇文章只是一個淺淺的起頭而已,還有一些像是 resolve type 等等概念沒有講。我們且戰且走,找個時間再來把這篇好好更新。

References

GraphQL: A query language for APIs.
graphql.org
GraphQL - Welcome
graphql-ruby.org

文章同步發表於 Medium


  • Find me at