SlideShare une entreprise Scribd logo
1  sur  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

Contenu connexe

Dernier

Neo4j - How KGs are shaping the future of Generative AI at AWS Summit London ...
Neo4j - How KGs are shaping the future of Generative AI at AWS Summit London ...Neo4j - How KGs are shaping the future of Generative AI at AWS Summit London ...
Neo4j - How KGs are shaping the future of Generative AI at AWS Summit London ...Neo4j
 
Salesforce Community Group Quito, Salesforce 101
Salesforce Community Group Quito, Salesforce 101Salesforce Community Group Quito, Salesforce 101
Salesforce Community Group Quito, Salesforce 101Paola De la Torre
 
How to Troubleshoot Apps for the Modern Connected Worker
How to Troubleshoot Apps for the Modern Connected WorkerHow to Troubleshoot Apps for the Modern Connected Worker
How to Troubleshoot Apps for the Modern Connected WorkerThousandEyes
 
Injustice - Developers Among Us (SciFiDevCon 2024)
Injustice - Developers Among Us (SciFiDevCon 2024)Injustice - Developers Among Us (SciFiDevCon 2024)
Injustice - Developers Among Us (SciFiDevCon 2024)Allon Mureinik
 
04-2024-HHUG-Sales-and-Marketing-Alignment.pptx
04-2024-HHUG-Sales-and-Marketing-Alignment.pptx04-2024-HHUG-Sales-and-Marketing-Alignment.pptx
04-2024-HHUG-Sales-and-Marketing-Alignment.pptxHampshireHUG
 
Boost PC performance: How more available memory can improve productivity
Boost PC performance: How more available memory can improve productivityBoost PC performance: How more available memory can improve productivity
Boost PC performance: How more available memory can improve productivityPrincipled Technologies
 
Top 5 Benefits OF Using Muvi Live Paywall For Live Streams
Top 5 Benefits OF Using Muvi Live Paywall For Live StreamsTop 5 Benefits OF Using Muvi Live Paywall For Live Streams
Top 5 Benefits OF Using Muvi Live Paywall For Live StreamsRoshan Dwivedi
 
A Call to Action for Generative AI in 2024
A Call to Action for Generative AI in 2024A Call to Action for Generative AI in 2024
A Call to Action for Generative AI in 2024Results
 
Automating Google Workspace (GWS) & more with Apps Script
Automating Google Workspace (GWS) & more with Apps ScriptAutomating Google Workspace (GWS) & more with Apps Script
Automating Google Workspace (GWS) & more with Apps Scriptwesley chun
 
Histor y of HAM Radio presentation slide
Histor y of HAM Radio presentation slideHistor y of HAM Radio presentation slide
Histor y of HAM Radio presentation slidevu2urc
 
Slack Application Development 101 Slides
Slack Application Development 101 SlidesSlack Application Development 101 Slides
Slack Application Development 101 Slidespraypatel2
 
Partners Life - Insurer Innovation Award 2024
Partners Life - Insurer Innovation Award 2024Partners Life - Insurer Innovation Award 2024
Partners Life - Insurer Innovation Award 2024The Digital Insurer
 
Presentation on how to chat with PDF using ChatGPT code interpreter
Presentation on how to chat with PDF using ChatGPT code interpreterPresentation on how to chat with PDF using ChatGPT code interpreter
Presentation on how to chat with PDF using ChatGPT code interpreternaman860154
 
08448380779 Call Girls In Civil Lines Women Seeking Men
08448380779 Call Girls In Civil Lines Women Seeking Men08448380779 Call Girls In Civil Lines Women Seeking Men
08448380779 Call Girls In Civil Lines Women Seeking MenDelhi Call girls
 
From Event to Action: Accelerate Your Decision Making with Real-Time Automation
From Event to Action: Accelerate Your Decision Making with Real-Time AutomationFrom Event to Action: Accelerate Your Decision Making with Real-Time Automation
From Event to Action: Accelerate Your Decision Making with Real-Time AutomationSafe Software
 
Finology Group – Insurtech Innovation Award 2024
Finology Group – Insurtech Innovation Award 2024Finology Group – Insurtech Innovation Award 2024
Finology Group – Insurtech Innovation Award 2024The Digital Insurer
 
Apidays Singapore 2024 - Building Digital Trust in a Digital Economy by Veron...
Apidays Singapore 2024 - Building Digital Trust in a Digital Economy by Veron...Apidays Singapore 2024 - Building Digital Trust in a Digital Economy by Veron...
Apidays Singapore 2024 - Building Digital Trust in a Digital Economy by Veron...apidays
 
08448380779 Call Girls In Friends Colony Women Seeking Men
08448380779 Call Girls In Friends Colony Women Seeking Men08448380779 Call Girls In Friends Colony Women Seeking Men
08448380779 Call Girls In Friends Colony Women Seeking MenDelhi Call girls
 
Unblocking The Main Thread Solving ANRs and Frozen Frames
Unblocking The Main Thread Solving ANRs and Frozen FramesUnblocking The Main Thread Solving ANRs and Frozen Frames
Unblocking The Main Thread Solving ANRs and Frozen FramesSinan KOZAK
 
IAC 2024 - IA Fast Track to Search Focused AI Solutions
IAC 2024 - IA Fast Track to Search Focused AI SolutionsIAC 2024 - IA Fast Track to Search Focused AI Solutions
IAC 2024 - IA Fast Track to Search Focused AI SolutionsEnterprise Knowledge
 

Dernier (20)

Neo4j - How KGs are shaping the future of Generative AI at AWS Summit London ...
Neo4j - How KGs are shaping the future of Generative AI at AWS Summit London ...Neo4j - How KGs are shaping the future of Generative AI at AWS Summit London ...
Neo4j - How KGs are shaping the future of Generative AI at AWS Summit London ...
 
Salesforce Community Group Quito, Salesforce 101
Salesforce Community Group Quito, Salesforce 101Salesforce Community Group Quito, Salesforce 101
Salesforce Community Group Quito, Salesforce 101
 
How to Troubleshoot Apps for the Modern Connected Worker
How to Troubleshoot Apps for the Modern Connected WorkerHow to Troubleshoot Apps for the Modern Connected Worker
How to Troubleshoot Apps for the Modern Connected Worker
 
Injustice - Developers Among Us (SciFiDevCon 2024)
Injustice - Developers Among Us (SciFiDevCon 2024)Injustice - Developers Among Us (SciFiDevCon 2024)
Injustice - Developers Among Us (SciFiDevCon 2024)
 
04-2024-HHUG-Sales-and-Marketing-Alignment.pptx
04-2024-HHUG-Sales-and-Marketing-Alignment.pptx04-2024-HHUG-Sales-and-Marketing-Alignment.pptx
04-2024-HHUG-Sales-and-Marketing-Alignment.pptx
 
Boost PC performance: How more available memory can improve productivity
Boost PC performance: How more available memory can improve productivityBoost PC performance: How more available memory can improve productivity
Boost PC performance: How more available memory can improve productivity
 
Top 5 Benefits OF Using Muvi Live Paywall For Live Streams
Top 5 Benefits OF Using Muvi Live Paywall For Live StreamsTop 5 Benefits OF Using Muvi Live Paywall For Live Streams
Top 5 Benefits OF Using Muvi Live Paywall For Live Streams
 
A Call to Action for Generative AI in 2024
A Call to Action for Generative AI in 2024A Call to Action for Generative AI in 2024
A Call to Action for Generative AI in 2024
 
Automating Google Workspace (GWS) & more with Apps Script
Automating Google Workspace (GWS) & more with Apps ScriptAutomating Google Workspace (GWS) & more with Apps Script
Automating Google Workspace (GWS) & more with Apps Script
 
Histor y of HAM Radio presentation slide
Histor y of HAM Radio presentation slideHistor y of HAM Radio presentation slide
Histor y of HAM Radio presentation slide
 
Slack Application Development 101 Slides
Slack Application Development 101 SlidesSlack Application Development 101 Slides
Slack Application Development 101 Slides
 
Partners Life - Insurer Innovation Award 2024
Partners Life - Insurer Innovation Award 2024Partners Life - Insurer Innovation Award 2024
Partners Life - Insurer Innovation Award 2024
 
Presentation on how to chat with PDF using ChatGPT code interpreter
Presentation on how to chat with PDF using ChatGPT code interpreterPresentation on how to chat with PDF using ChatGPT code interpreter
Presentation on how to chat with PDF using ChatGPT code interpreter
 
08448380779 Call Girls In Civil Lines Women Seeking Men
08448380779 Call Girls In Civil Lines Women Seeking Men08448380779 Call Girls In Civil Lines Women Seeking Men
08448380779 Call Girls In Civil Lines Women Seeking Men
 
From Event to Action: Accelerate Your Decision Making with Real-Time Automation
From Event to Action: Accelerate Your Decision Making with Real-Time AutomationFrom Event to Action: Accelerate Your Decision Making with Real-Time Automation
From Event to Action: Accelerate Your Decision Making with Real-Time Automation
 
Finology Group – Insurtech Innovation Award 2024
Finology Group – Insurtech Innovation Award 2024Finology Group – Insurtech Innovation Award 2024
Finology Group – Insurtech Innovation Award 2024
 
Apidays Singapore 2024 - Building Digital Trust in a Digital Economy by Veron...
Apidays Singapore 2024 - Building Digital Trust in a Digital Economy by Veron...Apidays Singapore 2024 - Building Digital Trust in a Digital Economy by Veron...
Apidays Singapore 2024 - Building Digital Trust in a Digital Economy by Veron...
 
08448380779 Call Girls In Friends Colony Women Seeking Men
08448380779 Call Girls In Friends Colony Women Seeking Men08448380779 Call Girls In Friends Colony Women Seeking Men
08448380779 Call Girls In Friends Colony Women Seeking Men
 
Unblocking The Main Thread Solving ANRs and Frozen Frames
Unblocking The Main Thread Solving ANRs and Frozen FramesUnblocking The Main Thread Solving ANRs and Frozen Frames
Unblocking The Main Thread Solving ANRs and Frozen Frames
 
IAC 2024 - IA Fast Track to Search Focused AI Solutions
IAC 2024 - IA Fast Track to Search Focused AI SolutionsIAC 2024 - IA Fast Track to Search Focused AI Solutions
IAC 2024 - IA Fast Track to Search Focused AI Solutions
 

En vedette

Content Methodology: A Best Practices Report (Webinar)
Content Methodology: A Best Practices Report (Webinar)Content Methodology: A Best Practices Report (Webinar)
Content Methodology: A Best Practices Report (Webinar)contently
 
How to Prepare For a Successful Job Search for 2024
How to Prepare For a Successful Job Search for 2024How to Prepare For a Successful Job Search for 2024
How to Prepare For a Successful Job Search for 2024Albert Qian
 
Social Media Marketing Trends 2024 // The Global Indie Insights
Social Media Marketing Trends 2024 // The Global Indie InsightsSocial Media Marketing Trends 2024 // The Global Indie Insights
Social Media Marketing Trends 2024 // The Global Indie InsightsKurio // The Social Media Age(ncy)
 
Trends In Paid Search: Navigating The Digital Landscape In 2024
Trends In Paid Search: Navigating The Digital Landscape In 2024Trends In Paid Search: Navigating The Digital Landscape In 2024
Trends In Paid Search: Navigating The Digital Landscape In 2024Search Engine Journal
 
5 Public speaking tips from TED - Visualized summary
5 Public speaking tips from TED - Visualized summary5 Public speaking tips from TED - Visualized summary
5 Public speaking tips from TED - Visualized summarySpeakerHub
 
ChatGPT and the Future of Work - Clark Boyd
ChatGPT and the Future of Work - Clark Boyd ChatGPT and the Future of Work - Clark Boyd
ChatGPT and the Future of Work - Clark Boyd Clark Boyd
 
Getting into the tech field. what next
Getting into the tech field. what next Getting into the tech field. what next
Getting into the tech field. what next Tessa Mero
 
Google's Just Not That Into You: Understanding Core Updates & Search Intent
Google's Just Not That Into You: Understanding Core Updates & Search IntentGoogle's Just Not That Into You: Understanding Core Updates & Search Intent
Google's Just Not That Into You: Understanding Core Updates & Search IntentLily Ray
 
Time Management & Productivity - Best Practices
Time Management & Productivity -  Best PracticesTime Management & Productivity -  Best Practices
Time Management & Productivity - Best PracticesVit Horky
 
The six step guide to practical project management
The six step guide to practical project managementThe six step guide to practical project management
The six step guide to practical project managementMindGenius
 
Beginners Guide to TikTok for Search - Rachel Pearson - We are Tilt __ Bright...
Beginners Guide to TikTok for Search - Rachel Pearson - We are Tilt __ Bright...Beginners Guide to TikTok for Search - Rachel Pearson - We are Tilt __ Bright...
Beginners Guide to TikTok for Search - Rachel Pearson - We are Tilt __ Bright...RachelPearson36
 
Unlocking the Power of ChatGPT and AI in Testing - A Real-World Look, present...
Unlocking the Power of ChatGPT and AI in Testing - A Real-World Look, present...Unlocking the Power of ChatGPT and AI in Testing - A Real-World Look, present...
Unlocking the Power of ChatGPT and AI in Testing - A Real-World Look, present...Applitools
 
12 Ways to Increase Your Influence at Work
12 Ways to Increase Your Influence at Work12 Ways to Increase Your Influence at Work
12 Ways to Increase Your Influence at WorkGetSmarter
 
Ride the Storm: Navigating Through Unstable Periods / Katerina Rudko (Belka G...
Ride the Storm: Navigating Through Unstable Periods / Katerina Rudko (Belka G...Ride the Storm: Navigating Through Unstable Periods / Katerina Rudko (Belka G...
Ride the Storm: Navigating Through Unstable Periods / Katerina Rudko (Belka G...DevGAMM Conference
 
Barbie - Brand Strategy Presentation
Barbie - Brand Strategy PresentationBarbie - Brand Strategy Presentation
Barbie - Brand Strategy PresentationErica Santiago
 
Good Stuff Happens in 1:1 Meetings: Why you need them and how to do them well
Good Stuff Happens in 1:1 Meetings: Why you need them and how to do them wellGood Stuff Happens in 1:1 Meetings: Why you need them and how to do them well
Good Stuff Happens in 1:1 Meetings: Why you need them and how to do them wellSaba Software
 

En vedette (20)

Content Methodology: A Best Practices Report (Webinar)
Content Methodology: A Best Practices Report (Webinar)Content Methodology: A Best Practices Report (Webinar)
Content Methodology: A Best Practices Report (Webinar)
 
How to Prepare For a Successful Job Search for 2024
How to Prepare For a Successful Job Search for 2024How to Prepare For a Successful Job Search for 2024
How to Prepare For a Successful Job Search for 2024
 
Social Media Marketing Trends 2024 // The Global Indie Insights
Social Media Marketing Trends 2024 // The Global Indie InsightsSocial Media Marketing Trends 2024 // The Global Indie Insights
Social Media Marketing Trends 2024 // The Global Indie Insights
 
Trends In Paid Search: Navigating The Digital Landscape In 2024
Trends In Paid Search: Navigating The Digital Landscape In 2024Trends In Paid Search: Navigating The Digital Landscape In 2024
Trends In Paid Search: Navigating The Digital Landscape In 2024
 
5 Public speaking tips from TED - Visualized summary
5 Public speaking tips from TED - Visualized summary5 Public speaking tips from TED - Visualized summary
5 Public speaking tips from TED - Visualized summary
 
ChatGPT and the Future of Work - Clark Boyd
ChatGPT and the Future of Work - Clark Boyd ChatGPT and the Future of Work - Clark Boyd
ChatGPT and the Future of Work - Clark Boyd
 
Getting into the tech field. what next
Getting into the tech field. what next Getting into the tech field. what next
Getting into the tech field. what next
 
Google's Just Not That Into You: Understanding Core Updates & Search Intent
Google's Just Not That Into You: Understanding Core Updates & Search IntentGoogle's Just Not That Into You: Understanding Core Updates & Search Intent
Google's Just Not That Into You: Understanding Core Updates & Search Intent
 
How to have difficult conversations
How to have difficult conversations How to have difficult conversations
How to have difficult conversations
 
Introduction to Data Science
Introduction to Data ScienceIntroduction to Data Science
Introduction to Data Science
 
Time Management & Productivity - Best Practices
Time Management & Productivity -  Best PracticesTime Management & Productivity -  Best Practices
Time Management & Productivity - Best Practices
 
The six step guide to practical project management
The six step guide to practical project managementThe six step guide to practical project management
The six step guide to practical project management
 
Beginners Guide to TikTok for Search - Rachel Pearson - We are Tilt __ Bright...
Beginners Guide to TikTok for Search - Rachel Pearson - We are Tilt __ Bright...Beginners Guide to TikTok for Search - Rachel Pearson - We are Tilt __ Bright...
Beginners Guide to TikTok for Search - Rachel Pearson - We are Tilt __ Bright...
 
Unlocking the Power of ChatGPT and AI in Testing - A Real-World Look, present...
Unlocking the Power of ChatGPT and AI in Testing - A Real-World Look, present...Unlocking the Power of ChatGPT and AI in Testing - A Real-World Look, present...
Unlocking the Power of ChatGPT and AI in Testing - A Real-World Look, present...
 
12 Ways to Increase Your Influence at Work
12 Ways to Increase Your Influence at Work12 Ways to Increase Your Influence at Work
12 Ways to Increase Your Influence at Work
 
ChatGPT webinar slides
ChatGPT webinar slidesChatGPT webinar slides
ChatGPT webinar slides
 
More than Just Lines on a Map: Best Practices for U.S Bike Routes
More than Just Lines on a Map: Best Practices for U.S Bike RoutesMore than Just Lines on a Map: Best Practices for U.S Bike Routes
More than Just Lines on a Map: Best Practices for U.S Bike Routes
 
Ride the Storm: Navigating Through Unstable Periods / Katerina Rudko (Belka G...
Ride the Storm: Navigating Through Unstable Periods / Katerina Rudko (Belka G...Ride the Storm: Navigating Through Unstable Periods / Katerina Rudko (Belka G...
Ride the Storm: Navigating Through Unstable Periods / Katerina Rudko (Belka G...
 
Barbie - Brand Strategy Presentation
Barbie - Brand Strategy PresentationBarbie - Brand Strategy Presentation
Barbie - Brand Strategy Presentation
 
Good Stuff Happens in 1:1 Meetings: Why you need them and how to do them well
Good Stuff Happens in 1:1 Meetings: Why you need them and how to do them wellGood Stuff Happens in 1:1 Meetings: Why you need them and how to do them well
Good Stuff Happens in 1:1 Meetings: Why you need them and how to do them well
 

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