Data Fetching with GraphQL and ActionCable · DeckType = GraphQL::ObjectType.define do name "Deck"...

37
Data Fetching with GraphQL and ActionCable Robert Mosolgo

Transcript of Data Fetching with GraphQL and ActionCable · DeckType = GraphQL::ObjectType.define do name "Deck"...

Page 1: Data Fetching with GraphQL and ActionCable · DeckType = GraphQL::ObjectType.define do name "Deck" description "A group of magic cards" field end ColorEnum = GraphQL::EnumType.define

Data Fetching with GraphQL and ActionCable

Robert Mosolgo

Page 2: Data Fetching with GraphQL and ActionCable · DeckType = GraphQL::ObjectType.define do name "Deck" description "A group of magic cards" field end ColorEnum = GraphQL::EnumType.define

Deck

• has many Cards • has a name

Card

• has a name • has 0+ colors

Page 3: Data Fetching with GraphQL and ActionCable · DeckType = GraphQL::ObjectType.define do name "Deck" description "A group of magic cards" field end ColorEnum = GraphQL::EnumType.define

Data-Driven UI

➡ Render ➡ User input ➡ Load data ➡ Render again ➡ User input again …

Page 4: Data Fetching with GraphQL and ActionCable · DeckType = GraphQL::ObjectType.define do name "Deck" description "A group of magic cards" field end ColorEnum = GraphQL::EnumType.define

Data-Driven UI

Page 5: Data Fetching with GraphQL and ActionCable · DeckType = GraphQL::ObjectType.define do name "Deck" description "A group of magic cards" field end ColorEnum = GraphQL::EnumType.define

“Canonical Representation”/api/v1/somethings/1.json

def as_json(options = {}) options[:methods] ||= [] options[:methods] += [:average_rating, :published_at_ago_in_words]

json = super(options)

if options[:published_at_format] json[:published_at] = published_at.strftime(options[:published_at_format]) end

json end

Page 6: Data Fetching with GraphQL and ActionCable · DeckType = GraphQL::ObjectType.define do name "Deck" description "A group of magic cards" field end ColorEnum = GraphQL::EnumType.define

“Canonical Representation”/api/v1/somethings/1.json

• #as_json becomes complex • Coupled to database • Overfetching • Underfetching

Page 7: Data Fetching with GraphQL and ActionCable · DeckType = GraphQL::ObjectType.define do name "Deck" description "A group of magic cards" field end ColorEnum = GraphQL::EnumType.define

GraphQLRequest Response

POST /graphql

{ deck(id: 1) { name cards { name colors } } }

{ "data": { "deck": { "name": "Turbo-Fog", "cards": [ { "name": "Fog", "colors": ["GREEN"] }, { "name": "Supreme Verdict", "colors": ["BLUE", "WHITE"] } ] } } }

name { name colors }

(id: 1)

Page 8: Data Fetching with GraphQL and ActionCable · DeckType = GraphQL::ObjectType.define do name "Deck" description "A group of magic cards" field end ColorEnum = GraphQL::EnumType.define

GraphQL Schematype Deck { name: String! average_rating: Int! cards: [Card] }

type Card { name: String! colors: [Color] combo_cards: [Card] }

enum Color { WHITE BLUE BLACK RED GREEN }

Page 9: Data Fetching with GraphQL and ActionCable · DeckType = GraphQL::ObjectType.define do name "Deck" description "A group of magic cards" field end ColorEnum = GraphQL::EnumType.define

GraphiQL

Page 10: Data Fetching with GraphQL and ActionCable · DeckType = GraphQL::ObjectType.define do name "Deck" description "A group of magic cards" field end ColorEnum = GraphQL::EnumType.define

GraphiQL

Page 11: Data Fetching with GraphQL and ActionCable · DeckType = GraphQL::ObjectType.define do name "Deck" description "A group of magic cards" field end ColorEnum = GraphQL::EnumType.define

Introspection

Page 12: Data Fetching with GraphQL and ActionCable · DeckType = GraphQL::ObjectType.define do name "Deck" description "A group of magic cards" field end ColorEnum = GraphQL::EnumType.define

GraphQL “Layer”

Storage

Application

GraphQL

Client

Page 13: Data Fetching with GraphQL and ActionCable · DeckType = GraphQL::ObjectType.define do name "Deck" description "A group of magic cards" field end ColorEnum = GraphQL::EnumType.define

GraphQL Recap

• GraphQL query → JSON response

• Schema: typed, self-documenting

• External-facing layer above business logic

Page 14: Data Fetching with GraphQL and ActionCable · DeckType = GraphQL::ObjectType.define do name "Deck" description "A group of magic cards" field end ColorEnum = GraphQL::EnumType.define

GraphQL & Rubygem “graphql”

• Types expose objects • Fields link types & values • Schema evaluates queries

Page 15: Data Fetching with GraphQL and ActionCable · DeckType = GraphQL::ObjectType.define do name "Deck" description "A group of magic cards" field end ColorEnum = GraphQL::EnumType.define

Types

Other types: Interface, Union, Input, Scalar

DeckType = GraphQL::ObjectType.define do name "Deck" description "A group of magic cards"

end

ColorEnum = GraphQL::EnumType.define do name "Color" description "Colors of Magic" value "WHITE" value "BLUE" value "BLACK" value "RED" value "GREEN" end

field :name, !types.String field :average_rating, !types.Float field :cards, types[CardType]

Page 16: Data Fetching with GraphQL and ActionCable · DeckType = GraphQL::ObjectType.define do name "Deck" description "A group of magic cards" field end ColorEnum = GraphQL::EnumType.define

Fields

# Calls the `name` method field :name, types.String

# This value cannot be `nil`: field :name, !types.String

# Returns a Card object field :commander, CardType

# This is an array of strings: field :previous_names, types[types.String]

Return types

Page 17: Data Fetching with GraphQL and ActionCable · DeckType = GraphQL::ObjectType.define do name "Deck" description "A group of magic cards" field end ColorEnum = GraphQL::EnumType.define

Fields

# Custom `resolve` behavior field :top_card, !CardType do description "Most popular card in the deck"

end

resolve -> (obj, args, ctx) { obj # => #<Deck> .cards .order("popularity DESC") .first }

Custom resolve

Page 18: Data Fetching with GraphQL and ActionCable · DeckType = GraphQL::ObjectType.define do name "Deck" description "A group of magic cards" field end ColorEnum = GraphQL::EnumType.define

Fields

# `argument` definitions field :cards, types[CardType] do

resolve -> (obj, args, ctx) {

# ... } end

argument :min_rating, types.Int

min_rating = args[:min_rating] || 0

Arguments

Page 19: Data Fetching with GraphQL and ActionCable · DeckType = GraphQL::ObjectType.define do name "Deck" description "A group of magic cards" field end ColorEnum = GraphQL::EnumType.define

SchemaEntry points

# "root" type -- entry point to the graph QueryType = GraphQL::ObjectType.define do name "Query"

end

field :deck, DeckType do argument :id, !types.Int resolve -> (obj, args, ctx) { Deck.find(args[:id]) } end

# { # deck(id: 1) { # # ... # } # }

Page 20: Data Fetching with GraphQL and ActionCable · DeckType = GraphQL::ObjectType.define do name "Deck" description "A group of magic cards" field end ColorEnum = GraphQL::EnumType.define

Schema

Schema = GraphQL::Schema.define do query QueryType

end

max_complexity 100 rescue_from(RecordNotFound) { |err| ... } # ...

Page 21: Data Fetching with GraphQL and ActionCable · DeckType = GraphQL::ObjectType.define do name "Deck" description "A group of magic cards" field end ColorEnum = GraphQL::EnumType.define

Schema

gem "graphiql-rails"

$.post( "/queries", {query: queryString}, function(response) { /* ... */ } )

Executing queries

class QueriesController < ApplicationController def create query_string = params[:query]

render json: response end end

response = Schema.execute(query_string)

Page 22: Data Fetching with GraphQL and ActionCable · DeckType = GraphQL::ObjectType.define do name "Deck" description "A group of magic cards" field end ColorEnum = GraphQL::EnumType.define

GraphQL & Ruby Recap

• Defining types & fields • Defining schema • Executing queries

Page 23: Data Fetching with GraphQL and ActionCable · DeckType = GraphQL::ObjectType.define do name "Deck" description "A group of magic cards" field end ColorEnum = GraphQL::EnumType.define

Catching on?

Page 24: Data Fetching with GraphQL and ActionCable · DeckType = GraphQL::ObjectType.define do name "Deck" description "A group of magic cards" field end ColorEnum = GraphQL::EnumType.define

Ideas from Facebook

gem “graphql-streaming”

Page 25: Data Fetching with GraphQL and ActionCable · DeckType = GraphQL::ObjectType.define do name "Deck" description "A group of magic cards" field end ColorEnum = GraphQL::EnumType.define

Live Updates

Page 26: Data Fetching with GraphQL and ActionCable · DeckType = GraphQL::ObjectType.define do name "Deck" description "A group of magic cards" field end ColorEnum = GraphQL::EnumType.define

Subscriptionssubscription { new_rating(deckId: 1) { deck { average_rating } } }

# Initial Response: { "data" => { "new_rating" => { "deck" => { "average_rating" => 3.1 } } } }

# Push: { "data" => { "new_rating" => { "deck" => { "average_rating" => 3.5 }

} } }

subscription

Page 27: Data Fetching with GraphQL and ActionCable · DeckType = GraphQL::ObjectType.define do name "Deck" description "A group of magic cards" field end ColorEnum = GraphQL::EnumType.define

ActionCable

# Listen for changes stream_from "new_rating:#{deck.id}"

# Publish changes ActionCable.server.broadcast("new_rating:#{deck_id}")

# Channels are updated

Pub-Sub

Page 28: Data Fetching with GraphQL and ActionCable · DeckType = GraphQL::ObjectType.define do name "Deck" description "A group of magic cards" field end ColorEnum = GraphQL::EnumType.define

ActionCable

HTTP 1

WebSocket

WebSocket Transport

Long-lived, two-way connection

Page 29: Data Fetching with GraphQL and ActionCable · DeckType = GraphQL::ObjectType.define do name "Deck" description "A group of magic cards" field end ColorEnum = GraphQL::EnumType.define

Subscriptionsvar queryString = ` subscription deckRating($deckId: Int!) { new_rating(deckId: $deckId) { deck { average_rating } } } `

var queryVariables = { deckId: 1 }

var onResponse = function(response) { // update your UI with response.data, response.errors }

App.graphqlChannel.fetch( queryString, queryVariables, onResponse )

Page 30: Data Fetching with GraphQL and ActionCable · DeckType = GraphQL::ObjectType.define do name "Deck" description "A group of magic cards" field end ColorEnum = GraphQL::EnumType.define

Live Updates

Page 31: Data Fetching with GraphQL and ActionCable · DeckType = GraphQL::ObjectType.define do name "Deck" description "A group of magic cards" field end ColorEnum = GraphQL::EnumType.define

Time-to-first-byte

Page 32: Data Fetching with GraphQL and ActionCable · DeckType = GraphQL::ObjectType.define do name "Deck" description "A group of magic cards" field end ColorEnum = GraphQL::EnumType.define

@defer{ deck(id: 1) { name win_percentage @defer average_rating @defer } }

# partial result { "data" => { "deck" => { "name" => "Red Deck Wins" } } }

# patch [ "data", "deck" ], { "average_rating" => 3.1 }

# patch [ "data", "deck" ], { "win_percentage" => 0.67 }

Page 33: Data Fetching with GraphQL and ActionCable · DeckType = GraphQL::ObjectType.define do name "Deck" description "A group of magic cards" field end ColorEnum = GraphQL::EnumType.define

@stream{ deck(id: 1) { name cards @stream { name } } }

# partial result { "data" => { "deck" => { "name" => "Fish" "cards" => [] } } }

# patch [ "data", "deck", "cards", 0 ] { "name" => "Merfolk Sovereign" }

# patch [ "data", "deck", "cards", 1 ] { "name" => "Cursecatcher" }

Page 34: Data Fetching with GraphQL and ActionCable · DeckType = GraphQL::ObjectType.define do name "Deck" description "A group of magic cards" field end ColorEnum = GraphQL::EnumType.define

@defer, @streamvar queryString = ` query getDeck($deckId: Int!) { deck(id: $deckId) { cards @stream { name } } } `

var queryVariables = { deckId: 1 }

App.graphqlChannel.fetch( queryString, queryVariables, onResponse )

var onResponse = function(response) { // update your UI with: // response.data, response.errors }

Page 35: Data Fetching with GraphQL and ActionCable · DeckType = GraphQL::ObjectType.define do name "Deck" description "A group of magic cards" field end ColorEnum = GraphQL::EnumType.define

graphql-streaming to-dos:

• ActionCable Deployment? • Multiplexing over one channel? • Complex loading states • 👍 @defer/@stream + Transfer-Encoding=Chunked

Page 36: Data Fetching with GraphQL and ActionCable · DeckType = GraphQL::ObjectType.define do name "Deck" description "A group of magic cards" field end ColorEnum = GraphQL::EnumType.define

Keep Exploring!

• graphql-persisted-queries • graphql-batch • view layer • your idea??

Page 37: Data Fetching with GraphQL and ActionCable · DeckType = GraphQL::ObjectType.define do name "Deck" description "A group of magic cards" field end ColorEnum = GraphQL::EnumType.define

github.com/rmosolgo/graphql-ruby github.com/rmosolgo/graphql-streaming

@rmosolgo