Building high-performance APIs forthe video game industry with Goliath,Grape, and EventMachineMatt E. PattersonDigimonkey ...
Matt PattersonSoftware Consultant(Ruby, Rails, Agile, etc.)Web software since 1998 (ColdFusion andPHP)Ruby and Rails since...
Game Web ServicesMust be fast.Must be reliable.Must be scalable......and able to handle sudden bursts(launches, new DLC, e...
Goliathopen-source, non-blocking, asychronous Ruby web serverframework from postrank-labsEventMachine reactor.HTTP parser,...
Ubiquitous Goliath“Hello, world” examplerequire goliathclass Hello < Goliath::APIdef response(env)[ 200, {}, "Hello, world...
Ubiquitous Goliath“Hello, world” examplerequire goliathclass Hello < Goliath::APIdef response(env)[ 200, {}, "Hello, world...
Ubiquitous Goliath“Hello, world” examplerequire goliathclass Hello < Goliath::APIdef response(env)[ 200, {}, "Hello, world...
Ubiquitous Goliath“Hello, world” examplerequire goliathclass Hello < Goliath::APIdef response(env)[ 200, {}, "Hello, world...
Ubiquitous Goliath“Hello, world” examplerequire goliathclass Hello < Goliath::APIdef response(env)[ 200, {}, "Hello, world...
What I needed...a full-featured APIversioned endpointsmultiple resourcesstandard RESTful CRUDJSON requests / responsessecu...
What I used...GrapeREST-like API micro-framework for RubyCarrierwave, Fog, MiniMagickfile uploads and S3 supportRspec + Fa...
Simple Goliath + Grape./app.rbrequire rubygemsrequire bundler/setuprequire goliathrequire em-synchrony/activerecordrequire...
Simple Goliath + Grape./app/api.rbDir["./app/apis/v1/*.rb"].each { |f| require f }class API < Grape::APImount APIv1::Unloc...
Simple Goliath + Grape./app/api.rbDir["./app/apis/v1/*.rb"].each { |f| require f }class API < Grape::APImount APIv1::Unloc...
Simple Goliath + Grape./app/apis/v1/unlocks.rbclass APIv1class Unlocks < Grape::APIversion v1, using: :path, format: :json...
Simple Goliath + Grape./app/apis/v1/unlocks.rbclass APIv1class Unlocks < Grape::APIversion v1, using: :path, format: :json...
Simple Goliath + Grapeback to our ./app/api.rbDir["./app/apis/v1/*.rb"].each { |f| require f }class API < Grape::APIhelper...
Simple Goliath + Grapeback to our ./app/api.rbDir["./app/apis/v1/*.rb"].each { |f| require f }class API < Grape::APIhelper...
Simple Goliath + Grapeback to our ./app/api.rbDir["./app/apis/v1/*.rb"].each { |f| require f }class API < Grape::APIhelper...
Simple Goliath + Grapeback to our ./app/api.rbDir["./app/apis/v1/*.rb"].each { |f| require f }class API < Grape::APIhelper...
Simple Goliath + Grapeback to our ./app/api.rbDir["./app/apis/v1/*.rb"].each { |f| require f }class API < Grape::APIhelper...
Simple Goliath + Grapeback to our ./app/api.rbDir["./app/apis/v1/*.rb"].each { |f| require f }class API < Grape::APIhelper...
Simple Goliath + Grape./app/views/api_v1/unlocks/show.json.rablattributes :id, :name, :code, :description, :created_atnode...
Simple Goliath + Grape./app/views/api_v1/unlocks/show.json.rablattributes :id, :name, :code, :description, :created_atnode...
Simple Goliath + Grape./app/views/api_v1/unlocks/show.json.rablattributes :id, :name, :code, :description, :created_atnode...
Simple Goliath + Grape./app/views/api_v1/unlocks/show.json.rablattributes :id, :name, :code, :description, :created_atnode...
Simple Goliath + Grape./app/views/api_v1/unlocks/show.json.rablattributes :id, :name, :code, :description, :created_atnode...
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_tagcl...
Simple Goliath + Grape./app/models/unlock.rbWell, that would have worked, if we had an Unlock model...require rocket_tagcl...
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:80...
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:80...
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:80...
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::Unloc...
Simple Goliath + Grape./app/api.rbDir["./app/apis/v1/*.rb"].each { |f| require f }class API < Grape::APImount APIv1::Unloc...
Simple Goliath + Grape./app/api.rbRemember me?Dir["./app/apis/v1/*.rb"].each { |f| require f }class API < Grape::APImount ...
Need communication?Build a Client Gem!Monday, June 10, 13
Need communication?Build a Client Gem!require virtusrequire rest_clientrequire multi_jsonmodule UnlocksClientclass Unlocki...
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: m...
Asynch MySQL GotchaUsed to Rails?If you’re not paying attention, you might do this...development:host: localhostadapter: e...
Asynch MySQL GotchaUsed to Rails?If you’re not paying attention, you might do this...development:host: localhostadapter: e...
Goliath Tipscurl is your friend on the command line.Console mode!ruby app.rb -svCusing Pry to debug stuff: Just add bindin...
LinksGoliath / Grape / EM Stuffhttps://github.com/postrank-labs/goliathhttps://github.com/igrigorik/em-synchronyhttps://gi...
Upcoming SlideShare
Loading in …5
×

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

3,002 views

Published on

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.

Published in: Technology, Business
0 Comments
22 Likes
Statistics
Notes
  • Be the first to comment

No Downloads
Views
Total views
3,002
On SlideShare
0
From Embeds
0
Number of Embeds
8
Actions
Shares
0
Downloads
0
Comments
0
Likes
22
Embeds 0
No embeds

No notes for slide

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

  1. 1. Building high-performance APIs forthe video game industry with Goliath,Grape, and EventMachineMatt E. PattersonDigimonkey StudiosMonday, June 10, 13
  2. 2. 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
  3. 3. 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
  4. 4. 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
  5. 5. Ubiquitous Goliath“Hello, world” examplerequire goliathclass Hello < Goliath::APIdef response(env)[ 200, {}, "Hello, world!" ]endendMonday, June 10, 13
  6. 6. Ubiquitous Goliath“Hello, world” examplerequire goliathclass Hello < Goliath::APIdef response(env)[ 200, {}, "Hello, world!" ]endend$ ruby hello.rb -sv -p 8000Monday, June 10, 13
  7. 7. 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
  8. 8. 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
  9. 9. 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
  10. 10. What I needed...a full-featured APIversioned endpointsmultiple resourcesstandard RESTful CRUDJSON requests / responsessecured requestslogginglocalization / translationfile attachments with S3storageasynchronous MySQLqueriestests!Monday, June 10, 13
  11. 11. 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
  12. 12. 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
  13. 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
  14. 14. 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
  15. 15. 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
  16. 16. 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
  17. 17. 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
  18. 18. 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
  19. 19. 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
  20. 20. 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
  21. 21. 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
  22. 22. 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
  23. 23. 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
  24. 24. 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
  25. 25. 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
  26. 26. 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
  27. 27. 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
  28. 28. Simple Goliath + Grape./app/models/unlock.rbWell, that would have worked, if we had an Unlock model...Monday, June 10, 13
  29. 29. 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
  30. 30. 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
  31. 31. Simple Goliath + GrapeTry it out!Monday, June 10, 13
  32. 32. Simple Goliath + GrapeTry it out!$ ruby app.rb -sv -p 8000Monday, June 10, 13
  33. 33. 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
  34. 34. 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
  35. 35. 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
  36. 36. Simple Goliath + Grape“Could the APIgive us userstoo? That’d begreat...”Monday, June 10, 13
  37. 37. 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
  38. 38. 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
  39. 39. 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
  40. 40. Need communication?Build a Client Gem!Monday, June 10, 13
  41. 41. 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
  42. 42. Asynch MySQL GotchaUsed to Rails?If you’re not paying attention, you might do this...Monday, June 10, 13
  43. 43. 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
  44. 44. 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
  45. 45. 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
  46. 46. 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
  47. 47. 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

×