Top Banner
Where little posts make a mighty magazine. Where readers become editors. Where giving inspiration is rewarded. From world to
32

From Ruby to Node.js

Jan 15, 2015

Download

Software

jubilem

This is a presentation I gave in Helsinki Node.js meetup (check http://helnode.io).

I have been implementing a realtime communication service with Ruby during my previous assignment. I've used Rails and lower level Ruby frameworks such as Sinatra and Resque workers.

I do like especially the Rack, since it enables building an efficient server stack. You can throw in middleware for throttling, authentication and for other tasks quite easily.

Ruby was a strong candidate also for my current project. I consider the Ruby code is more readable than JavaScript. However, once I understood what ECMAScript 6 brings in, I was sold to Node.js. Generators will enable actually very similar implementations than the Ruby's Rack stack. In my opinion, JavaScript will finally become mature with JS1.7 as the "callback spaghetti" will be soon history."
Welcome message from author
This document is posted to help you gain knowledge. Please leave a comment to let me know what you think about it! Share it to your friends and learn new things together.
Transcript
Page 1: From Ruby to Node.js

Where little posts make a mighty magazine. Where readers become editors. Where giving inspiration is rewarded.

From world

to

Page 2: From Ruby to Node.js

The task:Backend for real-time communication app

Page 3: From Ruby to Node.js

Why Ruby?

Page 4: From Ruby to Node.js

2011

Page 5: From Ruby to Node.js

Elegant language & environment

1. Beautiful Language: Dynamic, OO, linear

Page 6: From Ruby to Node.js

### Distribute a system message#def find_and_distribute(message_id) message = Message.find(message_id) raise SystemError, "Message not found" if message.nil? message.start_distribution return message.to_jsonrescue SystemError log.error "[API] distribute fail: "#{e.message}”end

Page 7: From Ruby to Node.js

1. Beautiful Language: Dynamic, OO, linear

2. Super ORM/ODM: ActiveRecord & Mongoid

Elegant language & environment

Page 8: From Ruby to Node.js

class MediaRobotUser include Mongoid::Document

# schema field :rss_url, type: String field :updated, type: DateTime field :update_freq, type: Integer, default: 5

# Validations validates :rss_url, :presence => true validates :update_freq, :presence => true def activate_user robot_queue = ”robot_update_#{self.id.to_s}" Resque.remove_schedule(robot_queue) schedule_options = { 'every' => '15m', 'custom_job_class' => “Bot::Jobs::UpdateMediaRobot", 'queue' => :update_media_robot, 'args' => self.id.to_s, } Resque.set_schedule(robot_queue, schedule_options) end end # class MediaRobotUser

Page 9: From Ruby to Node.js

1. Beautiful Language: Dynamic, OO, linear

2. Super ORM/ODM: ActiveRecord & Mongoid

3. Robust libraries: Community focus on libs selected for Rails

Elegant language & environment

Page 10: From Ruby to Node.js
Page 11: From Ruby to Node.js

1. Beautiful Language: Dynamic, OO, linear

2. Super ORM/ODM: ActiveRecord & Mongoid

3. Robust libraries: Rails gives focus on library development

4. Killer server stack for API: Thin + Rack + Sinatra

Elegant language & environment

Page 12: From Ruby to Node.js

Dedicated async web server for Ruby appsInstead of complex Apache/Passenger setup, you download thin and just run your app with it.

Layer between web servers and web frameworksDe facto standard in Ruby world: choose any ruby web server, choose any ruby framework, throw own middleware into soup and it just works!Simple HTTP request handler on top of RackNo MVC bloat for a simple, RESTful API application, but want still use same robust libraries Rails apps use. Just write functionality for your GET /my/api endpoint and go!

Page 13: From Ruby to Node.js

# Set Rails session to OAuth2 access token convert middlewareuse Auth::RailsSessionConverter

# Set OAuth2 handleruse Rack::OAuth2::Server

# launch Sinatra REST API serverrequire File.dirname(__FILE__) + '/app/access/restapi'map('/rest/vp5/') { run Access::RESTAPI }

2. Define Rack configuration (e.g. myrack.ru):

3. Run web server:

1. Write Sinatra web app:### API call to get backend version.#get '/system/version' do authenticate!('int_api_user') content_type :json status 200 return (JSON :data_api_version => 'proto v5.0')end

$ bundle exec thin –R myrack.ru –p 9400 -e development start

Ruby ≠ Rails! Sinatra, Resque and many other cool frameworks

Could map other apps to different paths

“homemade” middleware block hooked in!

No fuzz

Page 14: From Ruby to Node.js

The next task: Backend for Media App

Page 15: From Ruby to Node.js

2013

Page 16: From Ruby to Node.js

So.. Let’s think about

Culture for unsecure

code?

Born in 2011 – are

the libs there?

Don’t want that callback

spaghetti!

DB drivers and

ODMs?Performance ?

How to keep running in

production?

Page 17: From Ruby to Node.js

/** * Static content delivery */ staticDelivery: function (req, res) { var tmpDir = lzconf.filedbdir + "/tmp"; file.mkdir(tmpDir, '777’, function() { var fileSource = lzconf.filedbdir + "/" + req.params[0]; // do the work easyimg.rescrop(options, function(err, image) { console.log('Resized and cropped: ', image); // send file res.sendfile(tmpTarget, {maxAge: 604800}, function(err) { if (err) { … } // remove temp file fs.unlink(tmpTarget, function(err, success) { …

}); }); }); }); }

Page 18: From Ruby to Node.js

function myApiFunc(callback){ /* * This pattern does NOT work! */ try { doSomeAsynchronousOperation(function (err) { if (err) throw (err); /* continue as normal */ }); } catch (ex) { callback(ex); }}

Source: http://www.joyent.com/developers/node/design/errors

Whaaat? Cannot use try…catch??

Page 19: From Ruby to Node.js

“Sadly, that seems to be the story of Node.JS and the frameworks that use it. Derby, Meteor, SocketStream– they all are relatively new and immature, and in some cases, lack critical functionality of a web framework or have serious issues with security. That sort of puts me, as a developer, in an odd position. I’ve determined Node.JS is a good platform for a project, but without reinventing the wheel, what framework do I use to speed up development?”

– Andrew Munsellhttps://www.andrewmunsell.com/blog/the-odd-state-of-nodejs-and-its-frameworks

Page 20: From Ruby to Node.js
Page 21: From Ruby to Node.js

“Sinatra inspired web development framework for node.js – insanely fast, flexible, and simple.”

Yes!

Powered by Connect

Page 22: From Ruby to Node.js

More over, future looked

promising:

Page 23: From Ruby to Node.js

“Next generation web framework for node js.”

Page 24: From Ruby to Node.js

Why it rocks

ECMAScript 6: Generators

$ node --harmony> function* giveMeFruit() {... yield "apple";... yield "banana";... }> var fruitMachine = giveMeFruit()> fruitMachine.next(){ value: 'apple', done: false }> fruitMachine.next(){ value: 'banana', done: false }> fruitMachine.next(){ value: undefined, done: true }> fruitMachine.next()Error: Generator has already finished at GeneratorFunctionPrototype.next (native) …

What an earth this has to do with a HTTP framework?

Page 25: From Ruby to Node.js

Why it rocks

co – “The ultimate generator based

flow-control goodness for nodejs (supports thunks, promises, etc)”

var co = require('co');

co(function *(){ console.log(“updating user...”); try { var user = yield Users.findOneById(‘123’).exec(); if (!user) throw new Error(“Cannot find user”); user.name = “juha”; yield Promise.promisify(user.save, user)(); } catch (err) { console.log(“Name update failed: ”, err); }})()

This Mongoose finder returns a promise by default

“Save” need to be transferred into promise first for yield

Try…catch as it should be

Page 26: From Ruby to Node.js

Generator flow control (co) is the heart of

Koa server

Why it rocks

Page 27: From Ruby to Node.js

var koa = require('koa’) , api = require('./v1/myApiModule’), ...

app.use(mount(‘/api/v1’, api));app.listen(8080);

Why it rocks

Additional middleware to handle routing, e.g. koa-mount

Custom middleware upstream & downstream

app.use(function *(next) { var start = new Date; yield next; var ms = new Date - start; this.set('X-Response-Time', ms + 'ms');});

Many needed middleware libs already ported, such as

* koa-session-mongo* koa-passport* koa-cors* koa-send

app.use(session());app.use(passport.username());

app.use(function *(next) { try { yield next; } catch (err) { this.status = err.status || 500; }})

More robust code

Page 28: From Ruby to Node.js

APIwith koa-router

Why it rocks

Page 29: From Ruby to Node.js

var Router = require('koa-router')var API = new Router()

API.get('/topics/:topicId', function *() { try { this.body = yield MySubSystem.getTopic({ id: this.params.topicId, filter: this.request.query.filter_by }); } catch (err) { if (err.name === "DocumentNotFoundError") { this.body = "Topic not found"; this.status = 404; } else { throw err; } }}

Why it rocks

Page 30: From Ruby to Node.js
Page 31: From Ruby to Node.js
Page 32: From Ruby to Node.js

Where little posts make a mighty magazine. Where readers become editors. Where giving inspiration is rewarded.

Thank you!