From Ruby to Node.js

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

description

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."

Transcript of From Ruby to Node.js

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

From world

to

The task:Backend for real-time communication app

Why Ruby?

2011

Elegant language & environment

1. Beautiful Language: Dynamic, OO, linear

### 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

1. Beautiful Language: Dynamic, OO, linear

2. Super ORM/ODM: ActiveRecord & Mongoid

Elegant language & environment

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

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

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

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!

# 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

The next task: Backend for Media App

2013

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?

/** * 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) { …

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

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??

“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

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

Yes!

Powered by Connect

More over, future looked

promising:

“Next generation web framework for 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?

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

Generator flow control (co) is the heart of

Koa server

Why it rocks

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

APIwith koa-router

Why it rocks

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

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

Thank you!