• Save
Building high-performance APIs for the video game industry with Goliath, Grape and EventMachine
Upcoming SlideShare
Loading in...5
×
 

Building high-performance APIs for the video game industry with Goliath, Grape and EventMachine

on

  • 1,453 views

The video game industry frequently tests the limits of web- and service-oriented architectures. Poor-performing launches have made it clear that gamers expect fast, responsive services if we're going ...

The video game industry frequently tests the limits of web- and service-oriented architectures. Poor-performing launches have made it clear that gamers expect fast, responsive services if we're going to make games that are in constant communication with centralized data networks. Having infrastructure out-of-the-gate that's quick, scalable, and tunable is a must. This talk is a brief delve into the non-blocking Ruby web framework, Goliath, and how we're using it to power numerous APIs in a service-oriented infrastructure that ties together our game titles, players, social outreach, and internal stakeholders.

Statistics

Views

Total Views
1,453
Views on SlideShare
1,451
Embed Views
2

Actions

Likes
11
Downloads
0
Comments
0

1 Embed 2

https://twitter.com 2

Accessibility

Categories

Upload Details

Uploaded via as Adobe PDF

Usage Rights

© All Rights Reserved

Report content

Flagged as inappropriate Flag as inappropriate
Flag as inappropriate

Select your reason for flagging this presentation as inappropriate.

Cancel
  • Full Name Full Name Comment goes here.
    Are you sure you want to
    Your message goes here
    Processing…
Post Comment
Edit your comment

Building high-performance APIs for the video game industry with Goliath, Grape and EventMachine Building high-performance APIs for the video game industry with Goliath, Grape and EventMachine Presentation Transcript

  • Building high-performance APIs forthe video game industry with Goliath,Grape, and EventMachineMatt E. PattersonDigimonkey StudiosMonday, June 10, 13
  • Matt PattersonSoftware Consultant(Ruby, Rails, Agile, etc.)Web software since 1998 (ColdFusion andPHP)Ruby and Rails since 2006 (Rails 1.0)Monday, June 10, 13
  • Game Web ServicesMust be fast.Must be reliable.Must be scalable......and able to handle sudden bursts(launches, new DLC, etc.)Monday, June 10, 13
  • Goliathopen-source, non-blocking, asychronous Ruby web serverframework from postrank-labsEventMachine reactor.HTTP parser, Rack, Ruby 1.9+, Ruby Fiberseach request executes in its own FiberMonday, June 10, 13
  • Ubiquitous Goliath“Hello, world” examplerequire goliathclass Hello < Goliath::APIdef response(env)[ 200, {}, "Hello, world!" ]endendMonday, June 10, 13
  • Ubiquitous Goliath“Hello, world” examplerequire goliathclass Hello < Goliath::APIdef response(env)[ 200, {}, "Hello, world!" ]endend$ ruby hello.rb -sv -p 8000Monday, June 10, 13
  • Ubiquitous Goliath“Hello, world” examplerequire goliathclass Hello < Goliath::APIdef response(env)[ 200, {}, "Hello, world!" ]endend$ ruby hello.rb -sv -p 8000[45584:INFO] 2013-05-28 11:52:52 :: Starting server on0.0.0.0:8000 in development mode. Watch out for stones.Monday, June 10, 13
  • Ubiquitous Goliath“Hello, world” examplerequire goliathclass Hello < Goliath::APIdef response(env)[ 200, {}, "Hello, world!" ]endend$ ruby hello.rb -sv -p 8000[45584:INFO] 2013-05-28 11:52:52 :: Starting server on0.0.0.0:8000 in development mode. Watch out for stones.$ curl http://lvh.me:8000/Monday, June 10, 13
  • Ubiquitous Goliath“Hello, world” examplerequire goliathclass Hello < Goliath::APIdef response(env)[ 200, {}, "Hello, world!" ]endend$ ruby hello.rb -sv -p 8000[45584:INFO] 2013-05-28 11:52:52 :: Starting server on0.0.0.0:8000 in development mode. Watch out for stones.$ curl http://lvh.me:8000/Hello, world!Monday, June 10, 13
  • What I needed...a full-featured APIversioned endpointsmultiple resourcesstandard RESTful CRUDJSON requests / responsessecured requestslogginglocalization / translationfile attachments with S3storageasynchronous MySQLqueriestests!Monday, June 10, 13
  • What I used...GrapeREST-like API micro-framework for RubyCarrierwave, Fog, MiniMagickfile uploads and S3 supportRspec + FactoryGirltestingRabl + MultiJsonJSON requests / responsesem-synchrony + mysql2async MySQLglobalize3localization / translationstandalone_migrationsRails-style migrationscapistranodeployment stuffMonday, June 10, 13
  • Simple Goliath + Grape./app.rbrequire rubygemsrequire bundler/setuprequire goliathrequire em-synchrony/activerecordrequire grapeDir["./app/models/*.rb"].each { |f| require f }require ./app/apiclass Application < Goliath::APIdef response(env)::API.call(env)endendMonday, June 10, 13
  • Simple Goliath + Grape./app/api.rbDir["./app/apis/v1/*.rb"].each { |f| require f }class API < Grape::APImount APIv1::Unlocksresource servicehealth dodesc "Returns a basic status report."get "/" doMultiJson.dump({status: OK,environment: Goliath::env })endendendMonday, June 10, 13
  • Simple Goliath + Grape./app/api.rbDir["./app/apis/v1/*.rb"].each { |f| require f }class API < Grape::APImount APIv1::Unlocksresource servicehealth dodesc "Returns a basic status report."get "/" doMultiJson.dump({status: OK,environment: Goliath::env })endendendMonday, June 10, 13
  • Simple Goliath + Grape./app/apis/v1/unlocks.rbclass APIv1class Unlocks < Grape::APIversion v1, using: :path, format: :jsonresource :unlocks do# GET /unlocks/1.jsondesc "Returns a single Unlock record by ID"get "/:id" dounlock = Unlock.find(params[:id])custom_render "api_v1/unlocks/show", unlock, 200endendendendMonday, June 10, 13
  • Simple Goliath + Grape./app/apis/v1/unlocks.rbclass APIv1class Unlocks < Grape::APIversion v1, using: :path, format: :jsonresource :unlocks do# GET /unlocks/1.jsondesc "Returns a single Unlock record by ID"get "/:id" dounlock = Unlock.find(params[:id])custom_render "api_v1/unlocks/show", unlock, 200endendendendWait, what?Monday, June 10, 13
  • Simple Goliath + Grapeback to our ./app/api.rbDir["./app/apis/v1/*.rb"].each { |f| require f }class API < Grape::APIhelpers dodef custom_render(rabl_template, object, status, args={})args[:format] ||= jsonargs[:success] ||= truerender_options = { format: args[:format] }render_options[:locals] = args[:locals] if args[:locals]data = Rabl::Renderer.new(rabl_template, object, render_options).render%({ "success": #{args[:success]}, "data": #{data} })endendmount APIv1::Unlocks...Monday, June 10, 13
  • Simple Goliath + Grapeback to our ./app/api.rbDir["./app/apis/v1/*.rb"].each { |f| require f }class API < Grape::APIhelpers dodef custom_render(rabl_template, object, status, args={})args[:format] ||= jsonargs[:success] ||= truerender_options = { format: args[:format] }render_options[:locals] = args[:locals] if args[:locals]data = Rabl::Renderer.new(rabl_template, object, render_options).render%({ "success": #{args[:success]}, "data": #{data} })endendmount APIv1::Unlocks...Monday, June 10, 13
  • Simple Goliath + Grapeback to our ./app/api.rbDir["./app/apis/v1/*.rb"].each { |f| require f }class API < Grape::APIhelpers dodef custom_render(rabl_template, object, status, args={})args[:format] ||= jsonargs[:success] ||= truerender_options = { format: args[:format] }render_options[:locals] = args[:locals] if args[:locals]data = Rabl::Renderer.new(rabl_template, object, render_options).render%({ "success": #{args[:success]}, "data": #{data} })endendmount APIv1::Unlocks...Monday, June 10, 13
  • Simple Goliath + Grapeback to our ./app/api.rbDir["./app/apis/v1/*.rb"].each { |f| require f }class API < Grape::APIhelpers dodef custom_render(rabl_template, object, status, args={})args[:format] ||= jsonargs[:success] ||= truerender_options = { format: args[:format] }render_options[:locals] = args[:locals] if args[:locals]data = Rabl::Renderer.new(rabl_template, object, render_options).render%({ "success": #{args[:success]}, "data": #{data} })endendmount APIv1::Unlocks...Monday, June 10, 13
  • Simple Goliath + Grapeback to our ./app/api.rbDir["./app/apis/v1/*.rb"].each { |f| require f }class API < Grape::APIhelpers dodef custom_render(rabl_template, object, status, args={})args[:format] ||= jsonargs[:success] ||= truerender_options = { format: args[:format] }render_options[:locals] = args[:locals] if args[:locals]data = Rabl::Renderer.new(rabl_template, object, render_options).render%({ "success": #{args[:success]}, "data": #{data} })endendmount APIv1::Unlocks...Monday, June 10, 13
  • Simple Goliath + Grapeback to our ./app/api.rbDir["./app/apis/v1/*.rb"].each { |f| require f }class API < Grape::APIhelpers dodef custom_render(rabl_template, object, status, args={})args[:format] ||= jsonargs[:success] ||= truerender_options = { format: args[:format] }render_options[:locals] = args[:locals] if args[:locals]data = Rabl::Renderer.new(rabl_template, object, render_options).render%({ "success": #{args[:success]}, "data": #{data} })endendmount APIv1::Unlocks...Monday, June 10, 13
  • Simple Goliath + Grape./app/views/api_v1/unlocks/show.json.rablattributes :id, :name, :code, :description, :created_atnode(:unique_tags) { |unlock| unlock.tags.uniq }child :images => :images doattributes :id, :caption, :mime_type, :urlendMonday, June 10, 13
  • Simple Goliath + Grape./app/views/api_v1/unlocks/show.json.rablattributes :id, :name, :code, :description, :created_atnode(:unique_tags) { |unlock| unlock.tags.uniq }child :images => :images doattributes :id, :caption, :mime_type, :urlendMonday, June 10, 13
  • Simple Goliath + Grape./app/views/api_v1/unlocks/show.json.rablattributes :id, :name, :code, :description, :created_atnode(:unique_tags) { |unlock| unlock.tags.uniq }child :images => :images doattributes :id, :caption, :mime_type, :urlendrenders =>Monday, June 10, 13
  • Simple Goliath + Grape./app/views/api_v1/unlocks/show.json.rablattributes :id, :name, :code, :description, :created_atnode(:unique_tags) { |unlock| unlock.tags.uniq }child :images => :images doattributes :id, :caption, :mime_type, :urlendrenders =>{ "id":1,"name":"FancyThing","code":"001FT","description":"Enim doloribus idminima.","unique_tags":["foo","bar","woot"],"images":[{"image":{"id":"1","caption":"dolor sitamet","mime_type":"image/jpg","url":"http://s3.amazon.com/whatever.jpg"}}],"created_at":1364916167.000000000 }Monday, June 10, 13
  • Simple Goliath + Grape./app/views/api_v1/unlocks/show.json.rablattributes :id, :name, :code, :description, :created_atnode(:unique_tags) { |unlock| unlock.tags.uniq }child :images => :images doattributes :id, :caption, :mime_type, :urlendrenders =>{ "success": true, "data": {"id":1,"name":"FancyThing","code":"001FT","description":"Enim doloribus idminima.","unique_tags":["foo","bar","woot"],"images":[{"image":{"id":"1","caption":"dolor sitamet","mime_type":"image/jpg","url":"http://s3.amazon.com/whatever.jpg"}}],"created_at":1364916167.000000000} }Monday, June 10, 13
  • Simple Goliath + Grape./app/models/unlock.rbWell, that would have worked, if we had an Unlock model...Monday, June 10, 13
  • Simple Goliath + Grape./app/models/unlock.rbWell, that would have worked, if we had an Unlock model...require rocket_tagclass Unlock < ActiveRecord::Basehas_many :images, as: :image_attachable, dependent: :destroyattr_taggable :tagsvalidates :name, presence: truevalidates :code, presence: trueendMonday, June 10, 13
  • Simple Goliath + Grape./app/models/unlock.rbWell, that would have worked, if we had an Unlock model...require rocket_tagclass Unlock < ActiveRecord::Basehas_many :images, as: :image_attachable, dependent: :destroyattr_taggable :tagsvalidates :name, presence: truevalidates :code, presence: trueendOrdinary ActiveRecord like you’re accustomed to...Monday, June 10, 13
  • Simple Goliath + GrapeTry it out!Monday, June 10, 13
  • Simple Goliath + GrapeTry it out!$ ruby app.rb -sv -p 8000Monday, June 10, 13
  • Simple Goliath + GrapeTry it out!$ ruby app.rb -sv -p 8000[45584:INFO] 2013-05-28 11:52:52 :: Starting server on0.0.0.0:8000 in development mode. Watch out for stones.Monday, June 10, 13
  • Simple Goliath + GrapeTry it out!$ ruby app.rb -sv -p 8000[45584:INFO] 2013-05-28 11:52:52 :: Starting server on0.0.0.0:8000 in development mode. Watch out for stones.$ curl http://lvh.me:8000/v1/unlocks/1Monday, June 10, 13
  • Simple Goliath + GrapeTry it out!$ ruby app.rb -sv -p 8000[45584:INFO] 2013-05-28 11:52:52 :: Starting server on0.0.0.0:8000 in development mode. Watch out for stones.$ curl http://lvh.me:8000/v1/unlocks/1{ "success": true, "data": {"id":1,"name":"FancyThing","code":"001FT","description":"Enim doloribus idminima.","unique_tags":["foo","bar","woot"],"images":[{"image":{"id":"1","caption":"dolor sitamet","mime_type":"image/jpg","url":"http://s3.amazon.com/whatever.jpg"}}],"created_at":1364916167.000000000}}} }Monday, June 10, 13
  • Simple Goliath + Grape“Could the APIgive us userstoo? That’d begreat...”Monday, June 10, 13
  • Simple Goliath + Grape./app/api.rbDir["./app/apis/v1/*.rb"].each { |f| require f }class API < Grape::APImount APIv1::Unlocksresource servicehealth dodesc "Returns a basic status report."get "/" doMultiJson.dump({status: OK,environment: Goliath::env })endendendMonday, June 10, 13
  • Simple Goliath + Grape./app/api.rbDir["./app/apis/v1/*.rb"].each { |f| require f }class API < Grape::APImount APIv1::Unlocksresource servicehealth dodesc "Returns a basic status report."get "/" doMultiJson.dump({status: OK,environment: Goliath::env })endendendRemember me?Monday, June 10, 13
  • Simple Goliath + Grape./app/api.rbRemember me?Dir["./app/apis/v1/*.rb"].each { |f| require f }class API < Grape::APImount APIv1::Unlocksmount APIv1::Usersresource servicehealth dodesc "Returns a basic status report."get "/" doMultiJson.dump({status: OK,environment: Goliath::env })endendendMonday, June 10, 13
  • Need communication?Build a Client Gem!Monday, June 10, 13
  • Need communication?Build a Client Gem!require virtusrequire rest_clientrequire multi_jsonmodule UnlocksClientclass Unlockinclude Virtusattribute :idattribute :nameattribute :codeattribute :descriptionattribute :tagsattr_accessor :media_rewardsdef self.find(id, params={})client = RestClient::Resource.new("#{BASE_URL)}/unlocks/#{id}")response = client.get({params: params})data = MultiJson.load(response)["data"]return nil if !response || data.empty?new(params)endendendMonday, June 10, 13
  • Asynch MySQL GotchaUsed to Rails?If you’re not paying attention, you might do this...Monday, June 10, 13
  • Asynch MySQL GotchaUsed to Rails?If you’re not paying attention, you might do this...development:host: localhostadapter: mysql2database: unlocks_devpool: 20timeout: 5000reconnect: trueusername: dbuserpassword: 123whateverMonday, June 10, 13
  • Asynch MySQL GotchaUsed to Rails?If you’re not paying attention, you might do this...development:host: localhostadapter: em_mysql2database: unlocks_devpool: 20timeout: 5000reconnect: trueusername: dbuserpassword: 123whateverMonday, June 10, 13
  • Asynch MySQL GotchaUsed to Rails?If you’re not paying attention, you might do this...development:host: localhostadapter: em_mysql2database: unlocks_devpool: 20timeout: 5000reconnect: trueusername: dbuserpassword: 123whateverYMMVMonday, June 10, 13
  • Goliath Tipscurl is your friend on the command line.Console mode!ruby app.rb -svCusing Pry to debug stuff: Just add binding.pryMonday, June 10, 13
  • LinksGoliath / Grape / EM Stuffhttps://github.com/postrank-labs/goliathhttps://github.com/igrigorik/em-synchronyhttps://github.com/intridea/grapeMatt Stuffcode.digimonkey.commepatterson.netgithub.com/mepattersontwitter.com/mepattersonMonday, June 10, 13