คุณอาจเคยได้ยินนักพัฒนาฟังดัง GraphQL ใน series นี้ เราชอบเรียนรู้เทคโนโลยีโดยการ使用它 และบทความนี้จะผ่านตัวอย่าง application ที่ใช้ GraphQL
What is GraphQL
GraphQL 是一個 query language 和 runtime 可用於建立 APIs 它佔據 development stack 中與 REST API 相似的位置但更靈活 與 REST 不同 GraphQL 允許 response formats 和 content 被 client 指定 就像 SQL SELECT statements 允許指定查詢結果 GraphQL 允許指定返回的 JSON data structures 遵循 SQL analogy GraphQL 不提供 WHERE clause 但識別應用程序對象上的字段應為 response 提供數據
GraphQL 正如其名 建模 APIs 彷彿應用程序是一個 data graph 雖然這描述可能不是你如何看待你的應用程序 但它是大多數系統使用的模型 可以用 JSON 表示的數據是一個 graph 因為 JSON 只是一個 directed graph 將應用程序視為通過 API 呈現 graph model 將使 GraphQL 更容易理解
Using GraphQL in an Application
現在我們在抽象層面描述了 GraphQL 讓我們實際建立一個使用 GraphQL 的 application 從定義 data model 或 graph 開始 去年我採用了一個新愛好 我正在學習彈 electric upright bass 以及一般音樂 所以當想出 demo app 時 音樂相關的例子浮現在腦海
例子中的 object types 是 Artist 和 Song Artists 有多個 Song 和一個 Song 關聯一個 Artist 每個 object type 有 attributes 如 name
Define the API
GraphQL 使用 SDL (Schema Definition Language) 有時在 GraphQL specification 中稱為 "type system definition language" GraphQL types 理論上可以用任何語言定義但最常見的 agnostic language 是 SDL 所以讓我們用 SDL 定義 API
type Artist {
name: String!
songs: [Song]
origin: [String]
}
type Song {
name: String!
artist: Artist
duration: Int
release: String
}
一個 Artist 有 name 是 String 驚嘆號意味著 field 是 non-null songs 是 Song objects 的陣列 和 origin 是 String 陣列 Song 類似但有一個奇特的 field release field 應該是 time 或 date type 但 GraphQL 沒有將該類型定義為 core type 為了 complete portability 不同 GraphQL implementations 之間 使用 String 我們將使用的 GraphQL implementation 有 Time type 添加 所以讓我們改變 Song 定義使 release field 成為 Time type 返回值將是 String 但通過將類型設為 Time 我們更準確地 document API
release: Time
最後一步是描述如何獲取一個或多個對象 這稱為 root 或對於 queries 是 query root 我們的 root 將只有一個 field 或 method 叫 artist 並要求 artist name
type Query {
artist(name: String!): Artist
}
Writing the Application
讓我們看看如何在 application 中使用這個 有幾個 GraphQL server implementations for Ruby 一些 approaches 要求上面的 SDL 被翻譯成 Ruby equivalent Agoo 一個我建立的 HTTP server 使用 SDL definition 原樣 和 Ruby code 是 plain vanilla Ruby 所以這就是我們將使用的
注意 Ruby classes 匹配 GraphQL types 通過讓 Ruby class names 匹配 GraphQL type names 我們不添加任何不必要的 complexity
class Artist
attr_reader :name
attr_reader :songs
attr_reader :origin
def initialize(name, origin)
@name = name
@songs = []
@origin = origin
end
# Only used by the Song to add itself to the artist.
def add_song(song)
@songs << song
end
end
class Song
attr_reader :name # string
attr_reader :artist # reference
attr_reader :duration # integer
attr_reader :release # time
def initialize(name, artist, duration, release)
@name = name
@artist = artist
@duration = duration
@release = release
artist.add_song(self)
end
end
Methods 匹配 Ruby classes 中的 fields 注意方法要么沒有參數要么 args={} 這是 GraphQL APIs 期望的 我們的 implementation 在這裡遵循 suit initialize methods 用於為例子設置數據 正如我們將看到的
Query root class 也需要被定義 注意 artist 方法匹配 SDL Query root type attr_reader 為 artists 也被添加 那將被 expose 給 API 只需將該 field 添加到 SDL document 中的 Query type
class Query
attr_reader :artists
def initialize(artists)
@artists = artists
end
def artist(args={})
@artists[args['name']]
end
end
GraphQL root (不要與 query root 混淆) 位於 query root 之上 GraphQL 定義它可選擇擁有三個 fields Ruby class 在這種情況下 implement query field 初始化器載入一些測試數據 為一個我喜歡聽的來自紐西蘭的 Indie band
class Schema
attr_reader :query
attr_reader :mutation
attr_reader :subscription
def initialize()
# Set up some data for testing.
artist = Artist.new('Fazerdaze', ['Morningside', 'Auckland', 'New Zealand'])
Song.new('Jennifer', artist, 240, Time.utc(2017, 5, 5))
Song.new('Lucky Girl', artist, 170, Time.utc(2017, 5, 5))
Song.new('Friends', artist, 194, Time.utc(2017, 5, 5))
Song.new('Reel', artist, 193, Time.utc(2015, 11, 2))
@artists = {artist.name => artist}
@query = Query.new(@artists)
end
end
最終設置是 implementation specific 在這裡 server 被初始化以包含 /graphql HTTP request path 的 handler 然後啟動
Agoo::Server.init(6464, 'root', thread_count: 1, graphql: '/graphql')
Agoo::Server.start()
GraphQL implementation 然後用之前定義的 SDL ($songs_sdl) 配置 然後 application sleep 而 server processes requests
Agoo::GraphQL.schema(Schema.new) {
Agoo::GraphQL.load($songs_sdl)
}
sleep
這個例子的代碼可以在 GitHub 上找到
Using the API
為了測試 API 你可以使用 web browser, Postman 或 curl
要嘗試的 GraphQL query 長這樣:
{
artist(name:"Fazerdaze") {
name
songs{
name
duration
}
}
}
查詢詢問名為 Fazerdaze 的 Artist 並在 JSON document 中返回 name 和 songs 對於每個 Song 返回 Song 的 name 和 duration 在 JSON object 中 輸出應該長這樣
{
"data": {
"artist": {
"name": "Fazerdaze",
"songs": [
{ "name": "Jennifer", "duration": 240 },
{ "name": "Lucky Girl", "duration": 170 },
{ "name": "Friends", "duration": 194 },
{ "name": "Reel", "duration": 193 }
]
}
}
}
在去除查詢中的可選空白後 使用 curl 進行 HTTP GET 應該返回該內容
curl -w "\n" 'localhost:6464/graphql?query=\{artist(name:"Fazerdaze")\{name,songs\{name,duration\}\}\}&indent=2'
嘗試更改查詢並將 duration 替換為 release 並注意 Ruby Time 到 JSON string 的轉換
Outro to the song
我們享受玩弄 GraphQL 並希望你跟著學到了一些東西 謝謝 你是個很棒的聽眾 如果你想聊更多 Ruby 我們會在酒吧賣周邊