rest-core
A modular Ruby REST client collection/infrastructure

avatar Lin Jen-Shin (godfat)


avatar Lin Jen-Shin (godfat)


avatar Lin Jen-Shin (godfat)


avatar Lin Jen-Shin (godfat)


  • Programmer at Cardinal Blue
  • Programming Language
  • Functional Programming (Haskell)

avatar Lin Jen-Shin (godfat)


  • Programmer at Cardinal Blue
  • Programming Language
  • Functional Programming (Haskell)
  • Ruby

web-services

facebook

rest-graph
A lightweight Facebook Graph API client

Facebook Apps


Facebook Apps




01 facebook = RestGraph.new # Facebook Graph API
02 
03 
04 



01 facebook = RestGraph.new # Facebook Graph API
02 facebook.get('4')
03 
04 



01 facebook = RestGraph.new # Facebook Graph API
02 facebook.get('4')
03 facebook.post('4/photos',
04   :source => File.open('...'))

facebook

web-services

Solution: rest-core

Inspired by faraday and Rack

Generalized from rest-graph

(Almost) Identical interface

iOS apps


iOS apps


iOS apps


  • Pic Collage (500k downloads)
    • Share to Facebook
    • Share to Twitter

iOS apps


  • Pic Collage (500k downloads)
    • Share to Facebook
    • Share to Twitter
    • Share to Mixi (planned)

iOS apps


  • Pic Collage (500k downloads)
    • Share to Facebook
    • Share to Twitter
    • Share to Mixi (planned)
    • Share to Whatever

Why better than other existing clients?


  • Little Dependency

Why better than other existing clients?


  • Little Dependency
  • Less Version conflicts

No Dependency Hell

dependency-hell

X


  • How to build a Github client with rest-core


  • How to build a Github client with rest-core
  • Rack and rest-core architecture
  • Put things together

  • How to build a Github client with rest-core
  • Rack and rest-core architecture
  • Put things together
01 Github = RestCore::Builder.client do
02 end
01 Github = RestCore::Builder.client do
02 
03 
04 
05 end
06 
07 
01 Github = RestCore::Builder.client do
02   use DefaultSite , 'https://api.github.com/users/'
03 
04 
05 end
06 
07 
01 Github = RestCore::Builder.client do
02   use DefaultSite , 'https://api.github.com/users/'
03   use JsonDecode  , true
04 
05 end
06 
07 
01 Github = RestCore::Builder.client do
02   use DefaultSite , 'https://api.github.com/users/'
03   use JsonDecode  , true
04   run RestClient
05 end
06 
07 
01 Github = RestCore::Builder.client do
02   use DefaultSite , 'https://api.github.com/users/'
03   use JsonDecode  , true
04   run RestClient
05 end
06 
07 Github.new.get('godfat')
01 Github = RestCore::Builder.client do
02   use DefaultSite , 'https://api.github.com/users/'
03   use JsonDecode  , true
04   run RestClient
05 end
06 
07 Github.new.get('godfat')
08 
09 {"type"=>"User","company"=>"cardinalblue",
10  "blog"=>"http://godfat.org","hireable"=>false,
11  "url"=>"https://api.github.com/users/godfat",
12  "followers"=>40,"html_url"=>"https://github.com/godfat",
13  "bio"=>nil,"created_at"=>"2008-05-15T18:33:24Z",
14  "avatar_url"=>"https://...","following"=>33,
15  "name"=>"Lin Jen-Shin (godfat)","location"=>"Taiwan",
16  "email"=>"godfat (XD) godfat.org","public_repos"=>62,
17  "id"=>10416,"login"=>"godfat","public_gists"=>59}
01 Github = RestCore::Builder.client do
02   use DefaultSite , 'https://api.github.com/users/'
03   use JsonDecode  , true
04   
05   run RestClient
06 end
07 
08 
09 
10 
11 
12 
13 
14 
15 
16 
17 
01 Github = RestCore::Builder.client do
02   use DefaultSite , 'https://api.github.com/users/'
03   use JsonDecode  , true
04   use Cache       , {}, 3600
05   run RestClient
06 end
07 
08 
09 
10 
11 
12 
13 
14 
15 
16 
17 
01 Github = RestCore::Builder.client do
02   use DefaultSite , 'https://api.github.com/users/'
03   use JsonDecode  , true
04   use Cache       , {}, 3600
05   run RestClient
06 end
07 
08 client = Github.new
09 
10 
11 
12 
13 
14 
15 
16 
17 
01 Github = RestCore::Builder.client do
02   use DefaultSite , 'https://api.github.com/users/'
03   use JsonDecode  , true
04   use Cache       , {}, 3600
05   run RestClient
06 end
07 
08 client = Github.new
09 client.get('godfat') # slow
10 
11 
12 
13 
14 
15 
16 
17 
01 Github = RestCore::Builder.client do
02   use DefaultSite , 'https://api.github.com/users/'
03   use JsonDecode  , true
04   use Cache       , {}, 3600
05   run RestClient
06 end
07 
08 client = Github.new
09 client.get('godfat') # slow
10 client.get('godfat') # cache hit
11 
12 
13 
14 
15 
16 
17 
01 Github = RestCore::Builder.client do
02   use DefaultSite , 'https://api.github.com/users/'
03   use JsonDecode  , true
04   use Cache       , {}, 3600
05   run RestClient
06 end
07 
08 client = Github.new
09 client.get('godfat') # slow
10 client.get('godfat') # cache hit
11 client.get('godfat') # cache hit
12 
13 
14 
15 
16 
17 
01 Github = RestCore::Builder.client do
02   use DefaultSite , 'https://api.github.com/users/'
03   use JsonDecode  , true
04   use Cache       , {}, 3600
05   run RestClient
06 end
07 
08 client = Github.new
09 client.get('godfat') # slow
10 client.get('godfat') # cache hit
11 client.get('godfat') # cache hit
12 client.get('godfat') # cache hit
13 
14 
15 
16 
17 
01 Github = RestCore::Builder.client do
02   use DefaultSite , 'https://api.github.com/users/'
03   use JsonDecode  , true
04   
05   use Cache       , {}, 3600
06   run RestClient
07 end
08 
09 
10 
11 
12 
13 
14 
15 
16 
17 
01 Github = RestCore::Builder.client do
02   use DefaultSite , 'https://api.github.com/users/'
03   use JsonDecode  , true
04   use CommonLogger, method(:puts)
05   use Cache       , {}, 3600
06   run RestClient
07 end
08 
09 
10 
11 
12 
13 
14 
15 
16 
17 
01 Github = RestCore::Builder.client do
02   use DefaultSite , 'https://api.github.com/users/'
03   use JsonDecode  , true
04   use CommonLogger, method(:puts)
05   use Cache       , {}, 3600
06   run RestClient
07 end
08 
09 client = Github.new
10 
11 
12 
13 
14 
15 
16 
17 
01 Github = RestCore::Builder.client do
02   use DefaultSite , 'https://api.github.com/users/'
03   use JsonDecode  , true
04   use CommonLogger, method(:puts)
05   use Cache       , {}, 3600
06   run RestClient
07 end
08 
09 client = Github.new
10 client.get('godfat') # slow
11 
12 
13 
14 
15 
16 
17 
01 Github = RestCore::Builder.client do
02   use DefaultSite , 'https://api.github.com/users/'
03   use JsonDecode  , true
04   use CommonLogger, method(:puts)
05   use Cache       , {}, 3600
06   run RestClient
07 end
08 
09 client = Github.new
10 client.get('godfat') # slow
11 # RestCore: spent 1.498819 Requested https://api.github.com/users/godfat
12 
13 
14 
15 
16 
17 
01 Github = RestCore::Builder.client do
02   use DefaultSite , 'https://api.github.com/users/'
03   use JsonDecode  , true
04   use CommonLogger, method(:puts)
05   use Cache       , {}, 3600
06   run RestClient
07 end
08 
09 client = Github.new
10 client.get('godfat') # slow
11 # RestCore: spent 1.498819 Requested https://api.github.com/users/godfat
12 client.get('godfat') # cache hit
13 
14 
15 
16 
17 
01 Github = RestCore::Builder.client do
02   use DefaultSite , 'https://api.github.com/users/'
03   use JsonDecode  , true
04   use CommonLogger, method(:puts)
05   use Cache       , {}, 3600
06   run RestClient
07 end
08 
09 client = Github.new
10 client.get('godfat') # slow
11 # RestCore: spent 1.498819 Requested https://api.github.com/users/godfat
12 client.get('godfat') # cache hit
13 # RestCore: spent 2.0e-05 CacheHit https://api.github.com/users/godfat
14 # RestCore: spent 6.9e-05 Requested https://api.github.com/users/godfat
15 
16 
17 

  • How to build a Github client with rest-core
  • Rack and rest-core architecture
  • Put things together

rack

config.ru



01 
02               use Doo
03 
04 
05 
06 

config.ru



01 
02               use Doo
03               use Coo
04 
05 
06 

config.ru



01 
02               use Doo
03               use Coo
04               use Boo
05 
06 

config.ru



01 
02               use Doo
03               use Coo
04               use Boo
05               run App
06 

config.ru



01             app = Rack::Builder.app do
02               use Doo
03               use Coo
04               use Boo
05               run App
06             end

app

01                         App

app-mid

01                 Boo.new(App)

app-mid-mid

01         Coo.new(Boo.new(App))

app-mid-mid-mid

01 Doo.new(Coo.new(Boo.new(App)))

app-mid-mid-mid-arrow

01 Doo.new(Coo.new(Boo.new(App)))

Composable and Reusable

Rack is for building servers

Why not do the same for clients?

(Web Application)

client-server

(Firefox) (Chrome) (Opera)

(Web Application)

server-client

(Twitter) (Github) (Facebook)

rack

middleware-large

middleware-large

middleware-large

RestClient

middleware-large

DefaultSite RestClient

middleware-large

DefaultSite JsonDecode RestClient

middleware-large

DefaultSite JsonDecode CommonLogger RestClient

middleware-large

DefaultSite JsonDecode CommonLogger Cache RestClient

But that's not the only story about rest-core

Recall what we did for a Rack app


01 app = Rack::Builder.app do
02   use Doo
03   use Coo
04   use Boo
05   run App
06 end
07 
08 
09 
10 

01 app = Rack::Builder.app do
02   use Doo
03   use Coo
04   use Boo
05   run App
06 end
07 
08 app.class           # Doo
09 
10 

01 app = Rack::Builder.app do
02   use Doo
03   use Coo
04   use Boo
05   run App
06 end
07 
08 app.class           # Doo
09 app.kind_of?(Class) # false
10 

01 app = Rack::Builder.app do
02   use Doo
03   use Coo
04   use Boo
05   run App
06 end
07 
08 app.class           # Doo
09 app.kind_of?(Class) # false
10 app.call(env)       # app is an instance

Rack apps are instances

While rest-core clients are classes


01 Github = RestCore::Builder.client do
02   use DefaultSite , 'https://api.github.com/users/'
03   use JsonDecode  , true
04   run RestClient
05 end
06 
07 
08 
09 
10 

01 Github = RestCore::Builder.client do
02   use DefaultSite , 'https://api.github.com/users/'
03   use JsonDecode  , true
04   run RestClient
05 end
06 
07 Github.class             # Class
08 
09 
10 

01 Github = RestCore::Builder.client do
02   use DefaultSite , 'https://api.github.com/users/'
03   use JsonDecode  , true
04   run RestClient
05 end
06 
07 Github.class             # Class
08 Github.kind_of?(Class)   # true
09 
10 

01 Github = RestCore::Builder.client do
02   use DefaultSite , 'https://api.github.com/users/'
03   use JsonDecode  , true
04   run RestClient
05 end
06 
07 Github.class             # Class
08 Github.kind_of?(Class)   # true
09 Github.new.get('godfat') # Github is a class which
10                          #  would produce instances

Why?

Why do we create classes instead of instances?
 

Why do we create classes instead of instances?
Just like what Rack and faraday did?

Why classes?


  • Instance states won't affect each other

Why classes?


  • Instance states won't affect each other
  • Singleton is evil too stateful,
    too much side-effect

Why classes?


  • Instance states won't affect each other
  • Singleton is evil too stateful,
    too much side-effect
  • Think of instances as connections

Take Twitter as an example

01 t = Twitter.new(key_and_secret)
02 
03 
04 
05 
06 
07 
08 
09 
10 
11 
12 
13 
14 
15 
16 
17 
18 
01 t = Twitter.new(key_and_secret)
02 
03 t.authorize_url! # which is calling
04   t.post('oauth/request_token', {}, {},
05          {:json_decode => false})
06 # underneath
07 
08 
09 
10 
11 
12 
13 
14 
15 
16 
17 
18 
01 t = Twitter.new(key_and_secret)
02 
03 t.authorize_url! # which is calling
04   t.post('oauth/request_token', {}, {},
05          {:json_decode => false})
06 # underneath
07 
08 # which is also equivalent to...
09   t.json_decode = false # state
10   t.post('oauth/request_token')
11 
12 
13 
14 
15 
16 
17 
18 
01 t = Twitter.new(key_and_secret)
02 
03 t.authorize_url! # which is calling
04   t.post('oauth/request_token', {}, {},
05          {:json_decode => false})
06 # underneath
07 
08 # which is also equivalent to...
09   t.json_decode = false # state
10   t.post('oauth/request_token')
11 
12 t.authorize!(verifier) # calling
13   t.post('oauth/access_token', {}, {},
14            {:verifier => verifier,
15             :json_decode => false})
16 # underneath
17 
18 
01 t = Twitter.new(key_and_secret)
02 
03 t.authorize_url! # which is calling
04   t.post('oauth/request_token', {}, {},
05          {:json_decode => false})
06 # underneath
07 
08 # which is also equivalent to...
09   t.json_decode = false # state
10   t.post('oauth/request_token')
11 
12 t.authorize!(verifier) # calling
13   t.post('oauth/access_token', {}, {},
14            {:verifier => verifier,
15             :json_decode => false})
16 # underneath
17 
18 t.tweet('but we want json_decode here')

  • How to build a Github client with rest-core
  • Rack and rest-core architecture
  • Put things together
01 github = Github.new
02 
03 
04 
05 
06 
07 
08 
09 
01 github = Github.new
02 twitter = Twitter.new
03 
04 
05 
06 
07 
08 
09 
01 github = Github.new
02 twitter = Twitter.new
03 linkedin = Linkedin.new
04 
05 
06 
07 
08 
09 
01 github = Github.new
02 twitter = Twitter.new
03 linkedin = Linkedin.new
04 facebook = Facebook.new
05 
06 
07 
08 
09 
01 github = Github.new
02 twitter = Twitter.new
03 linkedin = Linkedin.new
04 facebook = Facebook.new
05 
06 github.get('godfat')
07 
08 
09 
01 github = Github.new
02 twitter = Twitter.new
03 linkedin = Linkedin.new
04 facebook = Facebook.new
05 
06 github.get('godfat')
07 twitter.get('user_timeline.json', :id => 'godfat')
08 
09 
01 github = Github.new
02 twitter = Twitter.new
03 linkedin = Linkedin.new
04 facebook = Facebook.new
05 
06 github.get('godfat')
07 twitter.get('user_timeline.json', :id => 'godfat')
08 linkedin.get('v1/people/~') # need authorize first
09 
01 github = Github.new
02 twitter = Twitter.new
03 linkedin = Linkedin.new
04 facebook = Facebook.new
05 
06 github.get('godfat')
07 twitter.get('user_timeline.json', :id => 'godfat')
08 linkedin.get('v1/people/~') # need authorize first
09 facebook.get('spellbook')

multiple-clients

more-clients

Tools used to build this slide

  • Landslide to make this slide
  • Pygments to do syntax highlighting
  • Adobe Illustrator to draw diagrams
  • TextMate to edit markdown
  • Nokogiri to fix generated HTML
  • Rib to interactively find out how to fix HTML
  • Firefox to view HTML