Bạn có thể đã nghe developer ca ngợi những kỳ quan của GraphQL. Trong series này chúng ta thích học technology bằng cách dùng nó, và bài này sẽ đi qua ứng dụng ví dụ dùng GraphQL.
GraphQL là gì
GraphQL là query language và runtime có thể dùng để build API. Nó giữ vị trí tương tự trong dev stack như REST API nhưng với sự linh hoạt hơn. Khác với REST, GraphQL cho phép response format và content được chỉ định bởi client. Giống như SQL SELECT cho phép chỉ định query results, GraphQL cho phép JSON data structures trả về được chỉ định. Theo analogy SQL, GraphQL không cung cấp clause WHERE nhưng xác định field trên application object nên cung cấp data cho response.
GraphQL, như tên gợi ý, model APIs như thể application là graph của data. Mặc dù mô tả này có thể không phải cách bạn view app, nó là model dùng trong hầu hết systems. Data có thể represent bằng JSON là graph vì JSON chỉ là directed graph. Nghĩ về application như presenting graph model qua API sẽ làm GraphQL dễ hiểu hơn.
Dùng GraphQL trong Ứng dụng
Bây giờ chúng ta đã mô tả GraphQL trong abstract, hãy xuống thực tế build ứng dụng dùng GraphQL bằng cách bắt đầu với định nghĩa data model hoặc graph. Năm ngoái tôi bắt đầu hobby mới. Tôi đang học chơi electric upright bass cũng như học về music nói chung, nên ví dụ music-related nảy ra khi nghĩ demo app.
Object types trong ví dụ là Artist và Song. Artists có multiple Song và Song associated với Artist. Mỗi object type có attribute như name.
Định nghĩa API
GraphQL dùng SDL (Schema Definition Language) đôi khi gọi là "type system definition language" trong GraphQL spec. GraphQL types về lý thuyết có thể định nghĩa bằng bất kỳ ngôn ngữ nào nhưng common agnostic language là SDL nên hãy dùng SDL định nghĩa API.
type Artist { name: String! songs: [Song] origin: [String] } type Song { name: String! artist: Artist duration: Int release: String } Artist có name là String. Dấu chấm than nghĩa field là non-null. songs là array của object Song, và origin là String array. Song tương tự nhưng có field lạ. Field release nên là time hoặc date type nhưng GraphQL không có type đó defined as core type. Để portability hoàn toàn giữa các GraphQL implementation, String được dùng. GraphQL implementation chúng ta sẽ dùng có type Time thêm vào nên hãy đổi Song definition để release field là type Time. Giá trị trả về sẽ là String, nhưng bằng cách set type thành Time chúng ta document API chính xác hơn.
release: Time Bước cuối là mô tả cách lấy một hoặc nhiều object. Điều này gọi là root hoặc, cho queries, query root. Root của chúng ta chỉ có một field hoặc method gọi là artist và yêu cầu artist name.
type Query { artist(name: String!): Artist } Viết Ứng dụng
Hãy xem cách dùng trong ứng dụng. Có vài implementation của GraphQL server cho Ruby. Một số approach yêu cầu SDL trên translate thành Ruby equivalent. Agoo, HTTP server tôi build, dùng SDL definition as-is và Ruby code là plain vanilla Ruby, nên đó là cái chúng ta sẽ dùng.
Lưu ý Ruby classes match GraphQL types. Bằng cách có Ruby class names match GraphQL type names, chúng ta không thêm complexity không cần thiết.
class Artist attr_reader :name attr_reader :songs attr_reader :origin def initialize(name, origin) @name = name @songs = [] @origin = origin end # Chỉ dùng bởi Song để add itself vào 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 endMethods match fields trong Ruby classes. Lưu ý methods hoặc không có argument hoặc args={}. Đây là cái GraphQL APIs expect và implementation của chúng ta follow suit. initialize methods dùng để setup data cho ví dụ, như chúng ta sẽ thấy.
Query root class cũng cần định nghĩa. Lưu ý method artist match SDL Query root type. attr_reader cho artists cũng được thêm. Đó sẽ exposed to API bằng cách thêm field đó vào Query type trong SDL document.
class Query attr_reader :artists def initialize(artists) @artists = artists end def artist(args={}) @artists[args['name']] end endGraphQL root (không nhầm với query root) nằm trên query root. GraphQL định nghĩa nó optionally có ba field. Ruby class, trong trường hợp này, implement trên field query. Initializer load data cho Indie band từ New Zealand tôi thích nghe.
class Schema attr_reader :query attr_reader :mutation attr_reader :subscription def initialize() # Setup data cho 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 endSetup cuối là implementation specific. Ở đây, server được init để include handler cho HTTP request path /graphql và start.
Agoo::Server.init(6464, 'root', thread_count: 1, graphql: '/graphql') Agoo::Server.start()GraphQL implementation sau đó configured với SDL định nghĩa trước ($songs_sdl) và app sleep mientras server process requests.
Agoo::GraphQL.schema(Schema.new) { Agoo::GraphQL.load($songs_sdl) } sleep Code cho ví dụ này có thể tìm thấy trên GitHub.
Dùng API
Để test API, bạn có thể dùng web browser, Postman hoặc curl.
GraphQL query để thử trông như sau:
{ artist(name:"Fazerdaze") { name songs{ name duration } } } Query hỏi Artist tên Fazerdaze và trả về name và songs trong JSON document. Cho mỗi Song name và duration được trả về trong JSON object. Output nên trông như sau.
{ "data": { "artist": { "name": "Fazerdaze", "songs": [ { "name": "Jennifer", "duration": 240 }, { "name": "Lucky Girl", "duration": 170 }, { "name": "Friends", "duration": 194 }, { "name": "Reel", "duration": 193 } ] } } }Sau khi loại bỏ optional whitespace trong query, HTTP GET với curl nên trả về content đó.
curl -w "\n" 'localhost:6464/graphql?query=\{artist(name:"Fazerdaze")\{name,songs\{name,duration\}\}\}&indent=2'Thử đổi query và replace duration với release và note conversion của Ruby Time thành JSON string.
Outro to the song
Chúng ta enjoy chơi với GraphQL và hy vọng bạn tagged along và học được gì đó trên đường. Cảm ơn, bạn là audience tuyệt vời. Nếu muốn nói thêm Ruby, chúng ta sẽ ở bar bán merch.