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."
6. ##
# 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_json
rescue SystemError
log.error "[API] distribute fail: "#{e.message}”
end
7. 1. Beautiful Language: Dynamic, OO, linear
2. Super ORM/ODM: ActiveRecord & Mongoid
Elegant language &
environment
8. 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
9. 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
10.
11. 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
12. Dedicated async web server for Ruby apps
Instead of complex Apache/Passenger setup, you
download thin and just run your app with it.
Layer between web servers and web frameworks
De 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 Rack
No 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!
13. # Set Rails session to OAuth2 access token convert middleware
use Auth::RailsSessionConverter
# Set OAuth2 handler
use Rack::OAuth2::Server
# launch Sinatra REST API server
require 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
16. 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?
17. /**
* 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) {
…
});
});
});
});
}
18. 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??
19. “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 Munsell
https://www.andrewmunsell.com/blog/the-odd-
state-of-nodejs-and-its-frameworks
20.
21. “Sinatra inspired web development
framework for node.js – insanely fast,
flexible, and simple.”
Yes!
Powered by Connect
24. 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?
25. 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
29. 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
30.
31.
32. Where little posts make a mighty magazine. Where readers become editors. Where giving inspiration is rewarded.
Thank you!
Notes de l'éditeur
From Ruby world to Node.js
CMSs are not for realtime system
Java felt more for enterprise level solution
PHP language felt outdated and did not found suitable stack
Rails MVC felt too heavy for API server purpose
Pure Ruby with dedicated servers and framework was the choice
Javascript famous for silently ignoring problems. Initially people did not handle CSRF. Sometimes neglected exception catching. Quickly written libs.
No use of reverse proxy for caching and load balancing is needed.