SlideShare a Scribd company logo
1 of 47
Building high-performance APIs for
the video game industry with Goliath,
Grape, and EventMachine
Matt E. Patterson
Digimonkey Studios
Monday, June 10, 13
Matt Patterson
Software Consultant
(Ruby, Rails, Agile, etc.)
Web software since 1998 (ColdFusion and
PHP)
Ruby and Rails since 2006 (Rails 1.0)
Monday, June 10, 13
Game Web Services
Must be fast.
Must be reliable.
Must be scalable...
...and able to handle sudden bursts
(launches, new DLC, etc.)
Monday, June 10, 13
Goliath
open-source, non-blocking, asychronous Ruby web server
framework from postrank-labs
EventMachine reactor.
HTTP parser, Rack, Ruby 1.9+, Ruby Fibers
each request executes in its own Fiber
Monday, June 10, 13
Ubiquitous Goliath
“Hello, world” example
require 'goliath'
class Hello < Goliath::API
def response(env)
[ 200, {}, "Hello, world!" ]
end
end
Monday, June 10, 13
Ubiquitous Goliath
“Hello, world” example
require 'goliath'
class Hello < Goliath::API
def response(env)
[ 200, {}, "Hello, world!" ]
end
end
$ ruby hello.rb -sv -p 8000
Monday, June 10, 13
Ubiquitous Goliath
“Hello, world” example
require 'goliath'
class Hello < Goliath::API
def response(env)
[ 200, {}, "Hello, world!" ]
end
end
$ ruby hello.rb -sv -p 8000
[45584:INFO] 2013-05-28 11:52:52 :: Starting server on
0.0.0.0:8000 in development mode. Watch out for stones.
Monday, June 10, 13
Ubiquitous Goliath
“Hello, world” example
require 'goliath'
class Hello < Goliath::API
def response(env)
[ 200, {}, "Hello, world!" ]
end
end
$ ruby hello.rb -sv -p 8000
[45584:INFO] 2013-05-28 11:52:52 :: Starting server on
0.0.0.0:8000 in development mode. Watch out for stones.
$ curl http://lvh.me:8000/
Monday, June 10, 13
Ubiquitous Goliath
“Hello, world” example
require 'goliath'
class Hello < Goliath::API
def response(env)
[ 200, {}, "Hello, world!" ]
end
end
$ ruby hello.rb -sv -p 8000
[45584:INFO] 2013-05-28 11:52:52 :: Starting server on
0.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 API
versioned endpoints
multiple resources
standard RESTful CRUD
JSON requests / responses
secured requests
logging
localization / translation
file attachments with S3
storage
asynchronous MySQL
queries
tests!
Monday, June 10, 13
What I used...
Grape
REST-like API micro-
framework for Ruby
Carrierwave, Fog, MiniMagick
file uploads and S3 support
Rspec + FactoryGirl
testing
Rabl + MultiJson
JSON requests / responses
em-synchrony + mysql2
async MySQL
globalize3
localization / translation
standalone_migrations
Rails-style migrations
capistrano
deployment stuff
Monday, June 10, 13
Simple Goliath + Grape
./app.rb
require 'rubygems'
require 'bundler/setup'
require 'goliath'
require 'em-synchrony/activerecord'
require 'grape'
Dir["./app/models/*.rb"].each { |f| require f }
require './app/api'
class Application < Goliath::API
def response(env)
::API.call(env)
end
end
Monday, June 10, 13
Simple Goliath + Grape
./app/api.rb
Dir["./app/apis/v1/*.rb"].each { |f| require f }
class API < Grape::API
mount APIv1::Unlocks
resource 'servicehealth' do
desc "Returns a basic status report."
get "/" do
MultiJson.dump({
status: 'OK',
environment: Goliath::env })
end
end
end
Monday, June 10, 13
Simple Goliath + Grape
./app/api.rb
Dir["./app/apis/v1/*.rb"].each { |f| require f }
class API < Grape::API
mount APIv1::Unlocks
resource 'servicehealth' do
desc "Returns a basic status report."
get "/" do
MultiJson.dump({
status: 'OK',
environment: Goliath::env })
end
end
end
Monday, June 10, 13
Simple Goliath + Grape
./app/apis/v1/unlocks.rb
class APIv1
class Unlocks < Grape::API
version 'v1', using: :path, format: :json
resource :unlocks do
# GET /unlocks/1.json
desc "Returns a single Unlock record by ID"
get "/:id" do
unlock = Unlock.find(params[:id])
custom_render "api_v1/unlocks/show", unlock, 200
end
end
end
end
Monday, June 10, 13
Simple Goliath + Grape
./app/apis/v1/unlocks.rb
class APIv1
class Unlocks < Grape::API
version 'v1', using: :path, format: :json
resource :unlocks do
# GET /unlocks/1.json
desc "Returns a single Unlock record by ID"
get "/:id" do
unlock = Unlock.find(params[:id])
custom_render "api_v1/unlocks/show", unlock, 200
end
end
end
end
Wait, what?
Monday, June 10, 13
Simple Goliath + Grape
back to our ./app/api.rb
Dir["./app/apis/v1/*.rb"].each { |f| require f }
class API < Grape::API
helpers do
def custom_render(rabl_template, object, status, args={})
args[:format] ||= 'json'
args[:success] ||= true
render_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} })
end
end
mount APIv1::Unlocks
...
Monday, June 10, 13
Simple Goliath + Grape
back to our ./app/api.rb
Dir["./app/apis/v1/*.rb"].each { |f| require f }
class API < Grape::API
helpers do
def custom_render(rabl_template, object, status, args={})
args[:format] ||= 'json'
args[:success] ||= true
render_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} })
end
end
mount APIv1::Unlocks
...
Monday, June 10, 13
Simple Goliath + Grape
back to our ./app/api.rb
Dir["./app/apis/v1/*.rb"].each { |f| require f }
class API < Grape::API
helpers do
def custom_render(rabl_template, object, status, args={})
args[:format] ||= 'json'
args[:success] ||= true
render_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} })
end
end
mount APIv1::Unlocks
...
Monday, June 10, 13
Simple Goliath + Grape
back to our ./app/api.rb
Dir["./app/apis/v1/*.rb"].each { |f| require f }
class API < Grape::API
helpers do
def custom_render(rabl_template, object, status, args={})
args[:format] ||= 'json'
args[:success] ||= true
render_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} })
end
end
mount APIv1::Unlocks
...
Monday, June 10, 13
Simple Goliath + Grape
back to our ./app/api.rb
Dir["./app/apis/v1/*.rb"].each { |f| require f }
class API < Grape::API
helpers do
def custom_render(rabl_template, object, status, args={})
args[:format] ||= 'json'
args[:success] ||= true
render_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} })
end
end
mount APIv1::Unlocks
...
Monday, June 10, 13
Simple Goliath + Grape
back to our ./app/api.rb
Dir["./app/apis/v1/*.rb"].each { |f| require f }
class API < Grape::API
helpers do
def custom_render(rabl_template, object, status, args={})
args[:format] ||= 'json'
args[:success] ||= true
render_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} })
end
end
mount APIv1::Unlocks
...
Monday, June 10, 13
Simple Goliath + Grape
./app/views/api_v1/unlocks/show.json.rabl
attributes :id, :name, :code, :description, :created_at
node(:unique_tags) { |unlock| unlock.tags.uniq }
child :images => :images do
attributes :id, :caption, :mime_type, :url
end
Monday, June 10, 13
Simple Goliath + Grape
./app/views/api_v1/unlocks/show.json.rabl
attributes :id, :name, :code, :description, :created_at
node(:unique_tags) { |unlock| unlock.tags.uniq }
child :images => :images do
attributes :id, :caption, :mime_type, :url
end
Monday, June 10, 13
Simple Goliath + Grape
./app/views/api_v1/unlocks/show.json.rabl
attributes :id, :name, :code, :description, :created_at
node(:unique_tags) { |unlock| unlock.tags.uniq }
child :images => :images do
attributes :id, :caption, :mime_type, :url
end
renders =>
Monday, June 10, 13
Simple Goliath + Grape
./app/views/api_v1/unlocks/show.json.rabl
attributes :id, :name, :code, :description, :created_at
node(:unique_tags) { |unlock| unlock.tags.uniq }
child :images => :images do
attributes :id, :caption, :mime_type, :url
end
renders =>
{ "id":1,"name":"Fancy
Thing","code":"001FT","description":"Enim doloribus id
minima.","unique_tags":["foo","bar","woot"],"images":
[{"image":{"id":"1","caption":"dolor sit
amet","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.rabl
attributes :id, :name, :code, :description, :created_at
node(:unique_tags) { |unlock| unlock.tags.uniq }
child :images => :images do
attributes :id, :caption, :mime_type, :url
end
renders =>
{ "success": true, "data": {"id":1,"name":"Fancy
Thing","code":"001FT","description":"Enim doloribus id
minima.","unique_tags":["foo","bar","woot"],"images":
[{"image":{"id":"1","caption":"dolor sit
amet","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.rb
Well, that would have worked, if we had an Unlock model...
Monday, June 10, 13
Simple Goliath + Grape
./app/models/unlock.rb
Well, that would have worked, if we had an Unlock model...
require 'rocket_tag'
class Unlock < ActiveRecord::Base
has_many :images, as: :image_attachable, dependent: :destroy
attr_taggable :tags
validates :name, presence: true
validates :code, presence: true
end
Monday, June 10, 13
Simple Goliath + Grape
./app/models/unlock.rb
Well, that would have worked, if we had an Unlock model...
require 'rocket_tag'
class Unlock < ActiveRecord::Base
has_many :images, as: :image_attachable, dependent: :destroy
attr_taggable :tags
validates :name, presence: true
validates :code, presence: true
end
Ordinary ActiveRecord like you’re accustomed to...
Monday, June 10, 13
Simple Goliath + Grape
Try it out!
Monday, June 10, 13
Simple Goliath + Grape
Try it out!
$ ruby app.rb -sv -p 8000
Monday, June 10, 13
Simple Goliath + Grape
Try it out!
$ ruby app.rb -sv -p 8000
[45584:INFO] 2013-05-28 11:52:52 :: Starting server on
0.0.0.0:8000 in development mode. Watch out for stones.
Monday, June 10, 13
Simple Goliath + Grape
Try it out!
$ ruby app.rb -sv -p 8000
[45584:INFO] 2013-05-28 11:52:52 :: Starting server on
0.0.0.0:8000 in development mode. Watch out for stones.
$ curl http://lvh.me:8000/v1/unlocks/1
Monday, June 10, 13
Simple Goliath + Grape
Try it out!
$ ruby app.rb -sv -p 8000
[45584:INFO] 2013-05-28 11:52:52 :: Starting server on
0.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":"Fancy
Thing","code":"001FT","description":"Enim doloribus id
minima.","unique_tags":["foo","bar","woot"],"images":
[{"image":{"id":"1","caption":"dolor sit
amet","mime_type":"image/jpg","url":"http://s3.amazon.com/
whatever.jpg"}}],"created_at":1364916167.000000000}}} }
Monday, June 10, 13
Simple Goliath + Grape
“Could the API
give us users
too? That’d be
great...”
Monday, June 10, 13
Simple Goliath + Grape
./app/api.rb
Dir["./app/apis/v1/*.rb"].each { |f| require f }
class API < Grape::API
mount APIv1::Unlocks
resource 'servicehealth' do
desc "Returns a basic status report."
get "/" do
MultiJson.dump({
status: 'OK',
environment: Goliath::env })
end
end
end
Monday, June 10, 13
Simple Goliath + Grape
./app/api.rb
Dir["./app/apis/v1/*.rb"].each { |f| require f }
class API < Grape::API
mount APIv1::Unlocks
resource 'servicehealth' do
desc "Returns a basic status report."
get "/" do
MultiJson.dump({
status: 'OK',
environment: Goliath::env })
end
end
end
Remember me?
Monday, June 10, 13
Simple Goliath + Grape
./app/api.rb
Remember me?
Dir["./app/apis/v1/*.rb"].each { |f| require f }
class API < Grape::API
mount APIv1::Unlocks
mount APIv1::Users
resource 'servicehealth' do
desc "Returns a basic status report."
get "/" do
MultiJson.dump({
status: 'OK',
environment: Goliath::env })
end
end
end
Monday, June 10, 13
Need communication?
Build a Client Gem!
Monday, June 10, 13
Need communication?
Build a Client Gem!
require 'virtus'
require 'rest_client'
require 'multi_json'
module UnlocksClient
class Unlock
include Virtus
attribute :id
attribute :name
attribute :code
attribute :description
attribute :tags
attr_accessor :media_rewards
def 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)
end
end
end
Monday, June 10, 13
Asynch MySQL Gotcha
Used to Rails?
If you’re not paying attention, you might do this...
Monday, June 10, 13
Asynch MySQL Gotcha
Used to Rails?
If you’re not paying attention, you might do this...
development:
host: localhost
adapter: mysql2
database: unlocks_dev
pool: 20
timeout: 5000
reconnect: true
username: dbuser
password: 123whatever
Monday, June 10, 13
Asynch MySQL Gotcha
Used to Rails?
If you’re not paying attention, you might do this...
development:
host: localhost
adapter: em_mysql2
database: unlocks_dev
pool: 20
timeout: 5000
reconnect: true
username: dbuser
password: 123whatever
Monday, June 10, 13
Asynch MySQL Gotcha
Used to Rails?
If you’re not paying attention, you might do this...
development:
host: localhost
adapter: em_mysql2
database: unlocks_dev
pool: 20
timeout: 5000
reconnect: true
username: dbuser
password: 123whatever
YMMV
Monday, June 10, 13
Goliath Tips
curl is your friend on the command line.
Console mode!
ruby app.rb -svC
using Pry to debug stuff: Just add binding.pry
Monday, June 10, 13
Links
Goliath / Grape / EM Stuff
https://github.com/
postrank-labs/goliath
https://github.com/
igrigorik/em-synchrony
https://github.com/
intridea/grape
Matt Stuff
code.digimonkey.com
mepatterson.net
github.com/mepatterson
twitter.com/mepatterson
Monday, June 10, 13

More Related Content

Recently uploaded

Accelerating Enterprise Software Engineering with Platformless
Accelerating Enterprise Software Engineering with PlatformlessAccelerating Enterprise Software Engineering with Platformless
Accelerating Enterprise Software Engineering with PlatformlessWSO2
 
Microservices, Docker deploy and Microservices source code in C#
Microservices, Docker deploy and Microservices source code in C#Microservices, Docker deploy and Microservices source code in C#
Microservices, Docker deploy and Microservices source code in C#Karmanjay Verma
 
Top 10 Hubspot Development Companies in 2024
Top 10 Hubspot Development Companies in 2024Top 10 Hubspot Development Companies in 2024
Top 10 Hubspot Development Companies in 2024TopCSSGallery
 
Irene Moetsana-Moeng: Stakeholders in Cybersecurity: Collaborative Defence fo...
Irene Moetsana-Moeng: Stakeholders in Cybersecurity: Collaborative Defence fo...Irene Moetsana-Moeng: Stakeholders in Cybersecurity: Collaborative Defence fo...
Irene Moetsana-Moeng: Stakeholders in Cybersecurity: Collaborative Defence fo...itnewsafrica
 
JET Technology Labs White Paper for Virtualized Security and Encryption Techn...
JET Technology Labs White Paper for Virtualized Security and Encryption Techn...JET Technology Labs White Paper for Virtualized Security and Encryption Techn...
JET Technology Labs White Paper for Virtualized Security and Encryption Techn...amber724300
 
So einfach geht modernes Roaming fuer Notes und Nomad.pdf
So einfach geht modernes Roaming fuer Notes und Nomad.pdfSo einfach geht modernes Roaming fuer Notes und Nomad.pdf
So einfach geht modernes Roaming fuer Notes und Nomad.pdfpanagenda
 
A Framework for Development in the AI Age
A Framework for Development in the AI AgeA Framework for Development in the AI Age
A Framework for Development in the AI AgeCprime
 
UiPath Community: Communication Mining from Zero to Hero
UiPath Community: Communication Mining from Zero to HeroUiPath Community: Communication Mining from Zero to Hero
UiPath Community: Communication Mining from Zero to HeroUiPathCommunity
 
A Glance At The Java Performance Toolbox
A Glance At The Java Performance ToolboxA Glance At The Java Performance Toolbox
A Glance At The Java Performance ToolboxAna-Maria Mihalceanu
 
Arizona Broadband Policy Past, Present, and Future Presentation 3/25/24
Arizona Broadband Policy Past, Present, and Future Presentation 3/25/24Arizona Broadband Policy Past, Present, and Future Presentation 3/25/24
Arizona Broadband Policy Past, Present, and Future Presentation 3/25/24Mark Goldstein
 
Bridging Between CAD & GIS: 6 Ways to Automate Your Data Integration
Bridging Between CAD & GIS:  6 Ways to Automate Your Data IntegrationBridging Between CAD & GIS:  6 Ways to Automate Your Data Integration
Bridging Between CAD & GIS: 6 Ways to Automate Your Data Integrationmarketing932765
 
Time Series Foundation Models - current state and future directions
Time Series Foundation Models - current state and future directionsTime Series Foundation Models - current state and future directions
Time Series Foundation Models - current state and future directionsNathaniel Shimoni
 
MuleSoft Online Meetup Group - B2B Crash Course: Release SparkNotes
MuleSoft Online Meetup Group - B2B Crash Course: Release SparkNotesMuleSoft Online Meetup Group - B2B Crash Course: Release SparkNotes
MuleSoft Online Meetup Group - B2B Crash Course: Release SparkNotesManik S Magar
 
Landscape Catalogue 2024 Australia-1.pdf
Landscape Catalogue 2024 Australia-1.pdfLandscape Catalogue 2024 Australia-1.pdf
Landscape Catalogue 2024 Australia-1.pdfAarwolf Industries LLC
 
The Future Roadmap for the Composable Data Stack - Wes McKinney - Data Counci...
The Future Roadmap for the Composable Data Stack - Wes McKinney - Data Counci...The Future Roadmap for the Composable Data Stack - Wes McKinney - Data Counci...
The Future Roadmap for the Composable Data Stack - Wes McKinney - Data Counci...Wes McKinney
 
Generative Artificial Intelligence: How generative AI works.pdf
Generative Artificial Intelligence: How generative AI works.pdfGenerative Artificial Intelligence: How generative AI works.pdf
Generative Artificial Intelligence: How generative AI works.pdfIngrid Airi González
 
2024 April Patch Tuesday
2024 April Patch Tuesday2024 April Patch Tuesday
2024 April Patch TuesdayIvanti
 
Unleashing Real-time Insights with ClickHouse_ Navigating the Landscape in 20...
Unleashing Real-time Insights with ClickHouse_ Navigating the Landscape in 20...Unleashing Real-time Insights with ClickHouse_ Navigating the Landscape in 20...
Unleashing Real-time Insights with ClickHouse_ Navigating the Landscape in 20...Alkin Tezuysal
 
Kuma Meshes Part I - The basics - A tutorial
Kuma Meshes Part I - The basics - A tutorialKuma Meshes Part I - The basics - A tutorial
Kuma Meshes Part I - The basics - A tutorialJoão Esperancinha
 
Testing tools and AI - ideas what to try with some tool examples
Testing tools and AI - ideas what to try with some tool examplesTesting tools and AI - ideas what to try with some tool examples
Testing tools and AI - ideas what to try with some tool examplesKari Kakkonen
 

Recently uploaded (20)

Accelerating Enterprise Software Engineering with Platformless
Accelerating Enterprise Software Engineering with PlatformlessAccelerating Enterprise Software Engineering with Platformless
Accelerating Enterprise Software Engineering with Platformless
 
Microservices, Docker deploy and Microservices source code in C#
Microservices, Docker deploy and Microservices source code in C#Microservices, Docker deploy and Microservices source code in C#
Microservices, Docker deploy and Microservices source code in C#
 
Top 10 Hubspot Development Companies in 2024
Top 10 Hubspot Development Companies in 2024Top 10 Hubspot Development Companies in 2024
Top 10 Hubspot Development Companies in 2024
 
Irene Moetsana-Moeng: Stakeholders in Cybersecurity: Collaborative Defence fo...
Irene Moetsana-Moeng: Stakeholders in Cybersecurity: Collaborative Defence fo...Irene Moetsana-Moeng: Stakeholders in Cybersecurity: Collaborative Defence fo...
Irene Moetsana-Moeng: Stakeholders in Cybersecurity: Collaborative Defence fo...
 
JET Technology Labs White Paper for Virtualized Security and Encryption Techn...
JET Technology Labs White Paper for Virtualized Security and Encryption Techn...JET Technology Labs White Paper for Virtualized Security and Encryption Techn...
JET Technology Labs White Paper for Virtualized Security and Encryption Techn...
 
So einfach geht modernes Roaming fuer Notes und Nomad.pdf
So einfach geht modernes Roaming fuer Notes und Nomad.pdfSo einfach geht modernes Roaming fuer Notes und Nomad.pdf
So einfach geht modernes Roaming fuer Notes und Nomad.pdf
 
A Framework for Development in the AI Age
A Framework for Development in the AI AgeA Framework for Development in the AI Age
A Framework for Development in the AI Age
 
UiPath Community: Communication Mining from Zero to Hero
UiPath Community: Communication Mining from Zero to HeroUiPath Community: Communication Mining from Zero to Hero
UiPath Community: Communication Mining from Zero to Hero
 
A Glance At The Java Performance Toolbox
A Glance At The Java Performance ToolboxA Glance At The Java Performance Toolbox
A Glance At The Java Performance Toolbox
 
Arizona Broadband Policy Past, Present, and Future Presentation 3/25/24
Arizona Broadband Policy Past, Present, and Future Presentation 3/25/24Arizona Broadband Policy Past, Present, and Future Presentation 3/25/24
Arizona Broadband Policy Past, Present, and Future Presentation 3/25/24
 
Bridging Between CAD & GIS: 6 Ways to Automate Your Data Integration
Bridging Between CAD & GIS:  6 Ways to Automate Your Data IntegrationBridging Between CAD & GIS:  6 Ways to Automate Your Data Integration
Bridging Between CAD & GIS: 6 Ways to Automate Your Data Integration
 
Time Series Foundation Models - current state and future directions
Time Series Foundation Models - current state and future directionsTime Series Foundation Models - current state and future directions
Time Series Foundation Models - current state and future directions
 
MuleSoft Online Meetup Group - B2B Crash Course: Release SparkNotes
MuleSoft Online Meetup Group - B2B Crash Course: Release SparkNotesMuleSoft Online Meetup Group - B2B Crash Course: Release SparkNotes
MuleSoft Online Meetup Group - B2B Crash Course: Release SparkNotes
 
Landscape Catalogue 2024 Australia-1.pdf
Landscape Catalogue 2024 Australia-1.pdfLandscape Catalogue 2024 Australia-1.pdf
Landscape Catalogue 2024 Australia-1.pdf
 
The Future Roadmap for the Composable Data Stack - Wes McKinney - Data Counci...
The Future Roadmap for the Composable Data Stack - Wes McKinney - Data Counci...The Future Roadmap for the Composable Data Stack - Wes McKinney - Data Counci...
The Future Roadmap for the Composable Data Stack - Wes McKinney - Data Counci...
 
Generative Artificial Intelligence: How generative AI works.pdf
Generative Artificial Intelligence: How generative AI works.pdfGenerative Artificial Intelligence: How generative AI works.pdf
Generative Artificial Intelligence: How generative AI works.pdf
 
2024 April Patch Tuesday
2024 April Patch Tuesday2024 April Patch Tuesday
2024 April Patch Tuesday
 
Unleashing Real-time Insights with ClickHouse_ Navigating the Landscape in 20...
Unleashing Real-time Insights with ClickHouse_ Navigating the Landscape in 20...Unleashing Real-time Insights with ClickHouse_ Navigating the Landscape in 20...
Unleashing Real-time Insights with ClickHouse_ Navigating the Landscape in 20...
 
Kuma Meshes Part I - The basics - A tutorial
Kuma Meshes Part I - The basics - A tutorialKuma Meshes Part I - The basics - A tutorial
Kuma Meshes Part I - The basics - A tutorial
 
Testing tools and AI - ideas what to try with some tool examples
Testing tools and AI - ideas what to try with some tool examplesTesting tools and AI - ideas what to try with some tool examples
Testing tools and AI - ideas what to try with some tool examples
 

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

  • 1. Building high-performance APIs for the video game industry with Goliath, Grape, and EventMachine Matt E. Patterson Digimonkey Studios Monday, June 10, 13
  • 2. Matt Patterson Software Consultant (Ruby, Rails, Agile, etc.) Web software since 1998 (ColdFusion and PHP) Ruby and Rails since 2006 (Rails 1.0) Monday, June 10, 13
  • 3. Game Web Services Must be fast. Must be reliable. Must be scalable... ...and able to handle sudden bursts (launches, new DLC, etc.) Monday, June 10, 13
  • 4. Goliath open-source, non-blocking, asychronous Ruby web server framework from postrank-labs EventMachine reactor. HTTP parser, Rack, Ruby 1.9+, Ruby Fibers each request executes in its own Fiber Monday, June 10, 13
  • 5. Ubiquitous Goliath “Hello, world” example require 'goliath' class Hello < Goliath::API def response(env) [ 200, {}, "Hello, world!" ] end end Monday, June 10, 13
  • 6. Ubiquitous Goliath “Hello, world” example require 'goliath' class Hello < Goliath::API def response(env) [ 200, {}, "Hello, world!" ] end end $ ruby hello.rb -sv -p 8000 Monday, June 10, 13
  • 7. Ubiquitous Goliath “Hello, world” example require 'goliath' class Hello < Goliath::API def response(env) [ 200, {}, "Hello, world!" ] end end $ ruby hello.rb -sv -p 8000 [45584:INFO] 2013-05-28 11:52:52 :: Starting server on 0.0.0.0:8000 in development mode. Watch out for stones. Monday, June 10, 13
  • 8. Ubiquitous Goliath “Hello, world” example require 'goliath' class Hello < Goliath::API def response(env) [ 200, {}, "Hello, world!" ] end end $ ruby hello.rb -sv -p 8000 [45584:INFO] 2013-05-28 11:52:52 :: Starting server on 0.0.0.0:8000 in development mode. Watch out for stones. $ curl http://lvh.me:8000/ Monday, June 10, 13
  • 9. Ubiquitous Goliath “Hello, world” example require 'goliath' class Hello < Goliath::API def response(env) [ 200, {}, "Hello, world!" ] end end $ ruby hello.rb -sv -p 8000 [45584:INFO] 2013-05-28 11:52:52 :: Starting server on 0.0.0.0:8000 in development mode. Watch out for stones. $ curl http://lvh.me:8000/ Hello, world! Monday, June 10, 13
  • 10. What I needed... a full-featured API versioned endpoints multiple resources standard RESTful CRUD JSON requests / responses secured requests logging localization / translation file attachments with S3 storage asynchronous MySQL queries tests! Monday, June 10, 13
  • 11. What I used... Grape REST-like API micro- framework for Ruby Carrierwave, Fog, MiniMagick file uploads and S3 support Rspec + FactoryGirl testing Rabl + MultiJson JSON requests / responses em-synchrony + mysql2 async MySQL globalize3 localization / translation standalone_migrations Rails-style migrations capistrano deployment stuff Monday, June 10, 13
  • 12. Simple Goliath + Grape ./app.rb require 'rubygems' require 'bundler/setup' require 'goliath' require 'em-synchrony/activerecord' require 'grape' Dir["./app/models/*.rb"].each { |f| require f } require './app/api' class Application < Goliath::API def response(env) ::API.call(env) end end Monday, June 10, 13
  • 13. Simple Goliath + Grape ./app/api.rb Dir["./app/apis/v1/*.rb"].each { |f| require f } class API < Grape::API mount APIv1::Unlocks resource 'servicehealth' do desc "Returns a basic status report." get "/" do MultiJson.dump({ status: 'OK', environment: Goliath::env }) end end end Monday, June 10, 13
  • 14. Simple Goliath + Grape ./app/api.rb Dir["./app/apis/v1/*.rb"].each { |f| require f } class API < Grape::API mount APIv1::Unlocks resource 'servicehealth' do desc "Returns a basic status report." get "/" do MultiJson.dump({ status: 'OK', environment: Goliath::env }) end end end Monday, June 10, 13
  • 15. Simple Goliath + Grape ./app/apis/v1/unlocks.rb class APIv1 class Unlocks < Grape::API version 'v1', using: :path, format: :json resource :unlocks do # GET /unlocks/1.json desc "Returns a single Unlock record by ID" get "/:id" do unlock = Unlock.find(params[:id]) custom_render "api_v1/unlocks/show", unlock, 200 end end end end Monday, June 10, 13
  • 16. Simple Goliath + Grape ./app/apis/v1/unlocks.rb class APIv1 class Unlocks < Grape::API version 'v1', using: :path, format: :json resource :unlocks do # GET /unlocks/1.json desc "Returns a single Unlock record by ID" get "/:id" do unlock = Unlock.find(params[:id]) custom_render "api_v1/unlocks/show", unlock, 200 end end end end Wait, what? Monday, June 10, 13
  • 17. Simple Goliath + Grape back to our ./app/api.rb Dir["./app/apis/v1/*.rb"].each { |f| require f } class API < Grape::API helpers do def custom_render(rabl_template, object, status, args={}) args[:format] ||= 'json' args[:success] ||= true render_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} }) end end mount APIv1::Unlocks ... Monday, June 10, 13
  • 18. Simple Goliath + Grape back to our ./app/api.rb Dir["./app/apis/v1/*.rb"].each { |f| require f } class API < Grape::API helpers do def custom_render(rabl_template, object, status, args={}) args[:format] ||= 'json' args[:success] ||= true render_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} }) end end mount APIv1::Unlocks ... Monday, June 10, 13
  • 19. Simple Goliath + Grape back to our ./app/api.rb Dir["./app/apis/v1/*.rb"].each { |f| require f } class API < Grape::API helpers do def custom_render(rabl_template, object, status, args={}) args[:format] ||= 'json' args[:success] ||= true render_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} }) end end mount APIv1::Unlocks ... Monday, June 10, 13
  • 20. Simple Goliath + Grape back to our ./app/api.rb Dir["./app/apis/v1/*.rb"].each { |f| require f } class API < Grape::API helpers do def custom_render(rabl_template, object, status, args={}) args[:format] ||= 'json' args[:success] ||= true render_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} }) end end mount APIv1::Unlocks ... Monday, June 10, 13
  • 21. Simple Goliath + Grape back to our ./app/api.rb Dir["./app/apis/v1/*.rb"].each { |f| require f } class API < Grape::API helpers do def custom_render(rabl_template, object, status, args={}) args[:format] ||= 'json' args[:success] ||= true render_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} }) end end mount APIv1::Unlocks ... Monday, June 10, 13
  • 22. Simple Goliath + Grape back to our ./app/api.rb Dir["./app/apis/v1/*.rb"].each { |f| require f } class API < Grape::API helpers do def custom_render(rabl_template, object, status, args={}) args[:format] ||= 'json' args[:success] ||= true render_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} }) end end mount APIv1::Unlocks ... Monday, June 10, 13
  • 23. Simple Goliath + Grape ./app/views/api_v1/unlocks/show.json.rabl attributes :id, :name, :code, :description, :created_at node(:unique_tags) { |unlock| unlock.tags.uniq } child :images => :images do attributes :id, :caption, :mime_type, :url end Monday, June 10, 13
  • 24. Simple Goliath + Grape ./app/views/api_v1/unlocks/show.json.rabl attributes :id, :name, :code, :description, :created_at node(:unique_tags) { |unlock| unlock.tags.uniq } child :images => :images do attributes :id, :caption, :mime_type, :url end Monday, June 10, 13
  • 25. Simple Goliath + Grape ./app/views/api_v1/unlocks/show.json.rabl attributes :id, :name, :code, :description, :created_at node(:unique_tags) { |unlock| unlock.tags.uniq } child :images => :images do attributes :id, :caption, :mime_type, :url end renders => Monday, June 10, 13
  • 26. Simple Goliath + Grape ./app/views/api_v1/unlocks/show.json.rabl attributes :id, :name, :code, :description, :created_at node(:unique_tags) { |unlock| unlock.tags.uniq } child :images => :images do attributes :id, :caption, :mime_type, :url end renders => { "id":1,"name":"Fancy Thing","code":"001FT","description":"Enim doloribus id minima.","unique_tags":["foo","bar","woot"],"images": [{"image":{"id":"1","caption":"dolor sit amet","mime_type":"image/jpg","url":"http://s3.amazon.com/ whatever.jpg"}}],"created_at":1364916167.000000000 } Monday, June 10, 13
  • 27. Simple Goliath + Grape ./app/views/api_v1/unlocks/show.json.rabl attributes :id, :name, :code, :description, :created_at node(:unique_tags) { |unlock| unlock.tags.uniq } child :images => :images do attributes :id, :caption, :mime_type, :url end renders => { "success": true, "data": {"id":1,"name":"Fancy Thing","code":"001FT","description":"Enim doloribus id minima.","unique_tags":["foo","bar","woot"],"images": [{"image":{"id":"1","caption":"dolor sit amet","mime_type":"image/jpg","url":"http://s3.amazon.com/ whatever.jpg"}}],"created_at":1364916167.000000000} } Monday, June 10, 13
  • 28. Simple Goliath + Grape ./app/models/unlock.rb Well, that would have worked, if we had an Unlock model... Monday, June 10, 13
  • 29. Simple Goliath + Grape ./app/models/unlock.rb Well, that would have worked, if we had an Unlock model... require 'rocket_tag' class Unlock < ActiveRecord::Base has_many :images, as: :image_attachable, dependent: :destroy attr_taggable :tags validates :name, presence: true validates :code, presence: true end Monday, June 10, 13
  • 30. Simple Goliath + Grape ./app/models/unlock.rb Well, that would have worked, if we had an Unlock model... require 'rocket_tag' class Unlock < ActiveRecord::Base has_many :images, as: :image_attachable, dependent: :destroy attr_taggable :tags validates :name, presence: true validates :code, presence: true end Ordinary ActiveRecord like you’re accustomed to... Monday, June 10, 13
  • 31. Simple Goliath + Grape Try it out! Monday, June 10, 13
  • 32. Simple Goliath + Grape Try it out! $ ruby app.rb -sv -p 8000 Monday, June 10, 13
  • 33. Simple Goliath + Grape Try it out! $ ruby app.rb -sv -p 8000 [45584:INFO] 2013-05-28 11:52:52 :: Starting server on 0.0.0.0:8000 in development mode. Watch out for stones. Monday, June 10, 13
  • 34. Simple Goliath + Grape Try it out! $ ruby app.rb -sv -p 8000 [45584:INFO] 2013-05-28 11:52:52 :: Starting server on 0.0.0.0:8000 in development mode. Watch out for stones. $ curl http://lvh.me:8000/v1/unlocks/1 Monday, June 10, 13
  • 35. Simple Goliath + Grape Try it out! $ ruby app.rb -sv -p 8000 [45584:INFO] 2013-05-28 11:52:52 :: Starting server on 0.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":"Fancy Thing","code":"001FT","description":"Enim doloribus id minima.","unique_tags":["foo","bar","woot"],"images": [{"image":{"id":"1","caption":"dolor sit amet","mime_type":"image/jpg","url":"http://s3.amazon.com/ whatever.jpg"}}],"created_at":1364916167.000000000}}} } Monday, June 10, 13
  • 36. Simple Goliath + Grape “Could the API give us users too? That’d be great...” Monday, June 10, 13
  • 37. Simple Goliath + Grape ./app/api.rb Dir["./app/apis/v1/*.rb"].each { |f| require f } class API < Grape::API mount APIv1::Unlocks resource 'servicehealth' do desc "Returns a basic status report." get "/" do MultiJson.dump({ status: 'OK', environment: Goliath::env }) end end end Monday, June 10, 13
  • 38. Simple Goliath + Grape ./app/api.rb Dir["./app/apis/v1/*.rb"].each { |f| require f } class API < Grape::API mount APIv1::Unlocks resource 'servicehealth' do desc "Returns a basic status report." get "/" do MultiJson.dump({ status: 'OK', environment: Goliath::env }) end end end Remember me? Monday, June 10, 13
  • 39. Simple Goliath + Grape ./app/api.rb Remember me? Dir["./app/apis/v1/*.rb"].each { |f| require f } class API < Grape::API mount APIv1::Unlocks mount APIv1::Users resource 'servicehealth' do desc "Returns a basic status report." get "/" do MultiJson.dump({ status: 'OK', environment: Goliath::env }) end end end Monday, June 10, 13
  • 40. Need communication? Build a Client Gem! Monday, June 10, 13
  • 41. Need communication? Build a Client Gem! require 'virtus' require 'rest_client' require 'multi_json' module UnlocksClient class Unlock include Virtus attribute :id attribute :name attribute :code attribute :description attribute :tags attr_accessor :media_rewards def 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) end end end Monday, June 10, 13
  • 42. Asynch MySQL Gotcha Used to Rails? If you’re not paying attention, you might do this... Monday, June 10, 13
  • 43. Asynch MySQL Gotcha Used to Rails? If you’re not paying attention, you might do this... development: host: localhost adapter: mysql2 database: unlocks_dev pool: 20 timeout: 5000 reconnect: true username: dbuser password: 123whatever Monday, June 10, 13
  • 44. Asynch MySQL Gotcha Used to Rails? If you’re not paying attention, you might do this... development: host: localhost adapter: em_mysql2 database: unlocks_dev pool: 20 timeout: 5000 reconnect: true username: dbuser password: 123whatever Monday, June 10, 13
  • 45. Asynch MySQL Gotcha Used to Rails? If you’re not paying attention, you might do this... development: host: localhost adapter: em_mysql2 database: unlocks_dev pool: 20 timeout: 5000 reconnect: true username: dbuser password: 123whatever YMMV Monday, June 10, 13
  • 46. Goliath Tips curl is your friend on the command line. Console mode! ruby app.rb -svC using Pry to debug stuff: Just add binding.pry Monday, June 10, 13
  • 47. Links Goliath / Grape / EM Stuff https://github.com/ postrank-labs/goliath https://github.com/ igrigorik/em-synchrony https://github.com/ intridea/grape Matt Stuff code.digimonkey.com mepatterson.net github.com/mepatterson twitter.com/mepatterson Monday, June 10, 13