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