Slide 1

Slide 1 text

Goliath Jacques Fuentes @jpfuentes2 github/jpfuentes2 EventMachine ruby 1.9 Fibers asynchronous pure awesomesness by PostRank

Slide 2

Slide 2 text

WHY? but your pants aren’t tight enough for node.js you want to be an async hipster

Slide 3

Slide 3 text

Draw Something by OMGPOP downloads on release day downloads w/in 50 days drawings per second 30K 50M 3K OMGSCALE

Slide 4

Slide 4 text

I don’t need roflscale... should I use this?

Slide 5

Slide 5 text

why the hell not? Goliath is... robust easy scalable performant ruby <3

Slide 6

Slide 6 text

3 EventMachine Ruby 1.9 Fibers em-synchrony Pillars

Slide 7

Slide 7 text

-Driven EventMachine Callbacks Fast Reactor Pattern

Slide 8

Slide 8 text

class Handler < EM::Connection include EM::HttpServer def initialize(mysql) @mysql = mysql end def process_http_request http = EM::HttpRequest.new('http://some-awesome-api').get http.callback do query = @mysql.query "insert awesome data" query.callback do |result| send_response "saved data!" end end end def send_response(content) r = EM::DelegatedHttpResponse.new(self) r.status = 200 r.content = content r.send_response end end EM.run do client = Mysql2::EM::Client.new EM::start_server("0.0.0.0", 8085, Handler, client) end

Slide 9

Slide 9 text

http://cdn.memegenerator.net/instances/400x/13756444.jpg

Slide 10

Slide 10 text

lightweight “green” threads Fibers co-operative lightweight “green” threads

Slide 11

Slide 11 text

http://www.igvita.com/2009/05/13/fibers-cooperative-scheduling-in-ruby/

Slide 12

Slide 12 text

combine Ruby 1.9 Fibers w/ EventMachine make async code look synchronous =>

Slide 13

Slide 13 text

require 'eventmachine' require 'em-http' require 'fiber' def async_fetch(url) f = Fiber.current http = EventMachine::HttpRequest.new(url).get :timeout => 10 http.callback { f.resume(http) } http.errback { f.resume(http) } return Fiber.yield end EventMachine.run do Fiber.new { puts "Setting up HTTP request #1" data = async_fetch('http://www.google.com/') puts "Fetched page #1: #{data.response_header.status}" EventMachine.stop }.resume end

Slide 14

Slide 14 text

em-synchrony by Ilya Grigorik Fiber “aware” library & wrappers makes async look sync Redis/Memcache ActiveRecord MySQL

Slide 15

Slide 15 text

3 EventMachine Ruby 1.9 Fibers em-synchrony Pillars

Slide 16

Slide 16 text

require 'goliath' class Hello < Goliath::API # default to JSON output, allow Yaml as secondary use Goliath::Rack::Render, ['json', 'yaml'] def response(env) [200, {}, "Hello World"] end end # > ruby hello.rb -sv # > [97570:INFO] 2011-02-15 00:33:51 :: Starting server on 0.0.0.0:9000 in development mode. Watch out for stones.

Slide 17

Slide 17 text

require 'config/setup' Configuration::Server.setup! class Server < Goliath::API use Goliath::Rack::Render, 'json' use Goliath::Rack::Heartbeat use Goliath::Rack::Params map "/sticky-notes" do run StickyNotesAPI.new end map "/todos" do run TodosAPI.new end map "/research-items" do run ResearchItemsAPI.new end map "/at-a-glance" do run AtAGlanceAPI.new end map "/tickets-graphs" do run TicketGraphsAPI.new end end

Slide 18

Slide 18 text

class TodosAPI < Goliath::API include Helper def get Todo.for_user(current_user).chronological.active end def put (@todo = Todo.find(params['id'])).update_attributes put_params publish @todo end def post @todo = Todo.create! post_params publish @todo end private def put_params params.slice('complete_date', 'text', 'due_date', 'assignee_id') end def post_params params.merge("creator_id" => current_user[:id]) end end

Slide 19

Slide 19 text

module Helper include Dashboard::RedisHelper def response(env) http_method = env['REQUEST_METHOD'].downcase return cors_options if http_method == 'options' invoke_api_using http_method end def publish(resource) event = { app_name: resource.class.app_name, ignore: current_user[:id], payload: resource } redis.publish 'dashboard', event.to_json end private def invoke_api_using(http_method) response = send(http_method) [200, cors_headers, response] end def cors_headers { 'Access-Control-Allow-Origin' => env['HTTP_ORIGIN'], 'Access-Control-Allow-Methods' => 'GET, PUT, POST, DELETE, OPTIONS', 'Access-Control-Allow-Headers' => 'Content-Type, Depth, User-Agent, X- Requested-With, Cache-Control', 'Access-Control-Allow-Credentials' => 'true' } end end

Slide 20

Slide 20 text

module Pusher def self.config YAML.load(File.read('config/pusher.yml'))[ENV['PUSHER_ENV']] end def self.start EM.run do @subscriber = EM::Hiredis.connect @subscriber.subscribe('dashboard') @sockets = Pusher::Sockets.new EventMachine::WebSocket.start(config) do |ws| ws.onopen { @sockets << ws } ws.onclose { @sockets.delete(ws) } end @subscriber.on(:message) do |channel, msg| event = Pusher::Event.new msg @sockets.send event end end end end

Slide 21

Slide 21 text

class Dashboard.Websocket constructor: (@options) -> _.extend @, Backbone.Events @start() start: -> @socket = new WebSocket @options.url @socket.onmessage = @onmessage @socket.onclose = @onclose @trigger "start", @ onmessage: (e) => data = JSON.parse e.data @trigger "message", data onclose: (e) => @reconnect() @trigger "closed", data reconnect: -> clearTimeout @_reconnect @_reconnect = setTimeout => @start() , 2000 class Dashboard.Application load: -> @socket() socket: -> @websocket = new Dashboard.Websocket url: "ws://0.0.0.0:2092" @websocket.start() @websocket.bind "message", @_receive, @ _receive: (event) -> app = @getApp event.app_name app.update event.payload

Slide 22

Slide 22 text

require 'goliath' require 'em-synchrony/activerecord' require 'yajl' require 'grape' class User < ActiveRecord::Base; end; class MyAPI < Grape::API version 'v1', :using => :path format :json resource 'users' do get "/" do User.all end get "/:id" do User.find(params['id']) end post "/create" do User.create(params['user']) end end end class APIServer < Goliath::API def response(env) MyAPI.call(env) end end

Slide 23

Slide 23 text

Questions? Jacques Fuentes @jpfuentes2 github/jpfuentes2