Speaker(s): Rebecca Mills, Junior Evangelist for Apache Cassandra and Patrick McFadin, Chief Evangelist for Apache Cassandra at DataStax
Ready to build something with Cassandra but don't want to use something heavy like Java or C#? Let's take a quick tour though the DataStax Ruby and NodeJS drivers. You'll see how they are easy to use and just as powerful as any other driver. Connect, insert, query like a pro and if there is a server failure? No problem. I'll show you how these drivers keep your app online despite server failures. This is a quick talk, but we'll have plenty of time to make you productive. It's that easy!
Choreo: Empowering the Future of Enterprise Software Engineering
Cassandra Day Chicago 2015: 0 to App Faster with Node.js and Ruby
1. 0 to App faster with NodeJS and Ruby
Rebecca Mills @RebccaMills
Patrick McFadin @PatrickMcFadin
2. The situation
• REST interface to Cassandra data
• Support CRUD operations
• It’s Friday and…
2
Umm yeah, and I’m going to
need that by Monday morning.
3. Choose your language
• Support for all Cassandra features
• Easy to work with
• Performant
3
7. DataStax NodeJS driver
• Works with current OSS Cassandra
• Apache Licensed
7
https://github.com/datastax/nodejs-driver
Based on node-cassandra-cql
by Jorge Bay
11. DataStax Cassandra Drivers
• Load Balancing Policies
• Retry Policies
• Asynchronous
• Prepared statements
• Connection and cluster management
11
A Cassandra Driver should have…
12. On to the code!
• RESTful web server
• Need a few helpers
12
+ +
13. REST methods
• Operates on the “USERS” table in Cassandra
13
POST Insert a user
GET Select a user
PUT Update a user
DELETE Delete a user
CREATE TABLE users (
firstname text,
lastname text,
age int,
email text,
city text,
PRIMARY KEY (lastname)
);
15. Connection for NodeJS
• Express as the web server
• body-parser to get POST data
15
var client = new cassandra.Client({
contactPoints: ['127.0.0.1'],
keyspace: 'demo',
policies: {
retry: new cassandra.policies.retry.RetryPolicy(),
loadBalancing: new cassandra.policies.loadBalancing.DCAwareRoundRobinPolicy('datacenter1')
}
}
);
var express = require('express')
var bodyParser = require('body-parser');
var cassandra = require('cassandra-driver');
16. var client = new cassandra.Client({
contactPoints: ['127.0.0.1'],
keyspace: 'demo',
policies: {
retry: new cassandra.policies.retry.RetryPolicy(),
loadBalancing: new cassandra.policies.loadBalancing.DCAwareRoundRobinPolicy('datacenter1')
}
}
);
Connection for NodeJS
• Express as the web server
• body-parser to get POST data
16
var express = require('express')
var bodyParser = require('body-parser');
var cassandra = require('cassandra-driver');
17. var client = new cassandra.Client({
contactPoints: ['127.0.0.1'],
keyspace: 'demo',
policies: {
retry: new cassandra.policies.retry.RetryPolicy(),
loadBalancing: new cassandra.policies.loadBalancing.DCAwareRoundRobinPolicy('datacenter1')
}
}
);
Connection for NodeJS
• Express as the web server
• body-parser to get POST data
17
var express = require('express')
var bodyParser = require('body-parser');
var cassandra = require('cassandra-driver');
18. var client = new cassandra.Client({
contactPoints: ['127.0.0.1'],
keyspace: 'demo',
policies: {
retry: new cassandra.policies.retry.RetryPolicy(),
loadBalancing: new cassandra.policies.loadBalancing.DCAwareRoundRobinPolicy('datacenter1')
}
}
);
Connection for NodeJS
• Express as the web server
• body-parser to get POST data
18
var express = require('express')
var bodyParser = require('body-parser');
var cassandra = require('cassandra-driver');
20. Insert a user with a POST
20
app.post('/users', function (req, res) {
var lastname = req.body.lastname;
var age = req.body.age;
var city = req.body.city;
var email = req.body.email;
var firstname = req.body.firstname;
var query = "INSERT INTO users (lastname, age, city, email, firstname) VALUES ( ?,?,?,?,?)";
var params = [lastname, age, city, email, firstname];
client.execute(query, params, {prepare: true}, function (err, result) {
if (!err) {
res.send("Inserted");
} else {
res.sendStatus(404)
}
})
})
21. Insert a user with a POST
21
app.post('/users', function (req, res) {
var lastname = req.body.lastname;
var age = req.body.age;
var city = req.body.city;
var email = req.body.email;
var firstname = req.body.firstname;
var query = "INSERT INTO users (lastname, age, city, email, firstname) VALUES ( ?,?,?,?,?)";
var params = [lastname, age, city, email, firstname];
client.execute(query, params, {prepare: true}, function (err, result) {
if (!err) {
res.send("Inserted");
} else {
res.sendStatus(404)
}
})
})
22. Insert a user with a POST
22
app.post('/users', function (req, res) {
var lastname = req.body.lastname;
var age = req.body.age;
var city = req.body.city;
var email = req.body.email;
var firstname = req.body.firstname;
var query = "INSERT INTO users (lastname, age, city, email, firstname) VALUES ( ?,?,?,?,?)";
var params = [lastname, age, city, email, firstname];
client.execute(query, params, {prepare: true}, function (err, result) {
if (!err) {
res.send("Inserted");
} else {
res.sendStatus(404)
}
})
})
23. Insert a user with a POST
23
app.post('/users', function (req, res) {
var lastname = req.body.lastname;
var age = req.body.age;
var city = req.body.city;
var email = req.body.email;
var firstname = req.body.firstname;
var query = "INSERT INTO users (lastname, age, city, email, firstname) VALUES ( ?,?,?,?,?)";
var params = [lastname, age, city, email, firstname];
client.execute(query, params, {prepare: true}, function (err, result) {
if (!err) {
res.send("Inserted");
} else {
res.sendStatus(404)
}
})
})
24. Select user with GET
24
app.get('/users/:lastname',function (req, res) {
var query = "SELECT lastname, age, city, email, firstname FROM users WHERE lastname= ?";
var params = [req.params.lastname];
client.execute(query, params, {prepare: true}, function (err, result) {
if (!err){
if ( result.rows.length > 0 ) {
var user = result.rows[0];
console.log("name = %s, age = %d", user.firstname, user.age);
res.send(user)
} else {
res.sendStatus(404);
}
}
});
})
25. Select user with GET
25
app.get('/users/:lastname',function (req, res) {
var query = "SELECT lastname, age, city, email, firstname FROM users WHERE lastname= ?";
var params = [req.params.lastname];
client.execute(query, params, {prepare: true}, function (err, result) {
if (!err){
if ( result.rows.length > 0 ) {
var user = result.rows[0];
console.log("name = %s, age = %d", user.firstname, user.age);
res.send(user)
} else {
res.sendStatus(404);
}
}
});
})
26. Select user with GET
26
app.get('/users/:lastname',function (req, res) {
var query = "SELECT lastname, age, city, email, firstname FROM users WHERE lastname= ?";
var params = [req.params.lastname];
client.execute(query, params, {prepare: true}, function (err, result) {
if (!err){
if ( result.rows.length > 0 ) {
var user = result.rows[0];
console.log("name = %s, age = %d", user.firstname, user.age);
res.send(user)
} else {
res.sendStatus(404);
}
}
});
})
27. Select user with GET
27
app.get('/users/:lastname',function (req, res) {
var query = "SELECT lastname, age, city, email, firstname FROM users WHERE lastname= ?";
var params = [req.params.lastname];
client.execute(query, params, {prepare: true}, function (err, result) {
if (!err){
if ( result.rows.length > 0 ) {
var user = result.rows[0];
console.log("name = %s, age = %d", user.firstname, user.age);
res.send(user)
} else {
res.sendStatus(404);
}
}
});
})
28. Select user with GET
28
app.get('/users/:lastname',function (req, res) {
var query = "SELECT lastname, age, city, email, firstname FROM users WHERE lastname= ?";
var params = [req.params.lastname];
client.execute(query, params, {prepare: true}, function (err, result) {
if (!err){
if ( result.rows.length > 0 ) {
var user = result.rows[0];
console.log("name = %s, age = %d", user.firstname, user.age);
res.send(user)
} else {
res.sendStatus(404);
}
}
});
})
29. Update a user with PUT
29
app.put('/users/:lastname', function (req, res) {
var age = req.body.age;
console.log("lastname = " + req.params.lastname + ", age= " + age);
var query = "UPDATE users SET age = ? WHERE lastname = ?";
var params = [age, req.params.lastname];
client.execute(query, params, {prepare: true}, function (err, result) {
if (!err) {
res.send("Updated");
} else {
res.sendStatus(404)
}
});
})
30. Remove a user with DELETE
30
app.delete('/users/:lastname', function (req, res) {
var query = "DELETE FROM users WHERE lastname = ?";
var params = [req.params.lastname];
client.execute(query, params, {prepare: true}, function (err, result) {
if (!err) {
res.send("Deleted");
} else {
res.sendStatus(404)
}
});
})
32. Connection with Ruby
• Sinatra as the web server
• JSON for returning formatted results
32
cluster = Cassandra.cluster(
:hosts => ['127.0.01'],
:load_balancing_policy => Cassandra::LoadBalancing::Policies::RoundRobin.new,
:retry_policy => Cassandra::Retry::Policies::Default.new,
logger: log)
keyspace = 'demo'
session = cluster.connect(keyspace)
require 'sinatra'
require 'JSON'
require 'cassandra'
require 'logger'
33. Connection with Ruby
• Sinatra as the web server
• JSON for returning formatted results
33
cluster = Cassandra.cluster(
:hosts => ['127.0.01'],
:load_balancing_policy => Cassandra::LoadBalancing::Policies::RoundRobin.new,
:retry_policy => Cassandra::Retry::Policies::Default.new,
logger: log)
keyspace = 'demo'
session = cluster.connect(keyspace)
require 'sinatra'
require 'JSON'
require 'cassandra'
require 'logger'
34. Connection with Ruby
• Sinatra as the web server
• JSON for returning formatted results
34
cluster = Cassandra.cluster(
:hosts => ['127.0.01'],
:load_balancing_policy => Cassandra::LoadBalancing::Policies::RoundRobin.new,
:retry_policy => Cassandra::Retry::Policies::Default.new,
logger: log)
keyspace = 'demo'
session = cluster.connect(keyspace)
require 'sinatra'
require 'JSON'
require 'cassandra'
require 'logger'
35. Retry Policies
• Retry requests on server errors
• Write Timeout
• Read Timeout
• Unavailable
35
36. Connection with Ruby
• Sinatra as the web server
• JSON for returning formatted results
36
cluster = Cassandra.cluster(
:hosts => ['127.0.01'],
:load_balancing_policy => Cassandra::LoadBalancing::Policies::RoundRobin.new,
:retry_policy => Cassandra::Retry::Policies::Default.new,
logger: log)
keyspace = 'demo'
session = cluster.connect(keyspace)
require 'sinatra'
require 'JSON'
require 'cassandra'
require 'logger'
37. Insert a user with a POST
37
post '/users' do
begin
session.execute(userInsertStatement, :arguments => [params[:firstname], params[:lastname],
params[:age].to_i, params[:city], params[:email]])
"Inserted"
rescue Exception => e
log.error 'Error in insert a user'
log.error(e)
halt(404)
end
end
userInsertStatement = session.prepare("INSERT INTO users (firstname, lastname, age, city, email)
VALUES (?,?,?,?,?)")
38. Insert a user with a POST
38
post '/users' do
begin
session.execute(userInsertStatement, :arguments => [params[:firstname], params[:lastname],
params[:age].to_i, params[:city], params[:email]])
"Inserted"
rescue Exception => e
log.error 'Error in insert a user'
log.error(e)
halt(404)
end
end
userInsertStatement = session.prepare("INSERT INTO users (firstname, lastname, age, city, email)
VALUES (?,?,?,?,?)")
…
39. Insert a user with a POST
39
post '/users' do
begin
session.execute(userInsertStatement, :arguments => [params[:firstname], params[:lastname],
params[:age].to_i, params[:city], params[:email]])
"Inserted"
rescue Exception => e
log.error 'Error in insert a user'
log.error(e)
halt(404)
end
end
userInsertStatement = session.prepare("INSERT INTO users (firstname, lastname, age, city, email)
VALUES (?,?,?,?,?)")
…
40. Select user with GET
40
get '/users/:lastname' do
begin
result = session.execute(userSelectStatement, :arguments => [params[:lastname]])
if result.size < 1
halt(404)
end
result.first.to_json
rescue Exception => e
log.error 'Error in select a user'
log.error(e)
halt(404)
end
end
userSelectStatement = session.prepare("SELECT firstname,lastname, age, email, city
FROM users where lastname = ?")
41. Select user with GET
41
get '/users/:lastname' do
begin
result = session.execute(userSelectStatement, :arguments => [params[:lastname]])
if result.size < 1
halt(404)
end
result.first.to_json
rescue Exception => e
log.error 'Error in select a user'
log.error(e)
halt(404)
end
end
userSelectStatement = session.prepare("SELECT firstname,lastname, age, email, city
FROM users where lastname = ?")…
42. Select user with GET
42
get '/users/:lastname' do
begin
result = session.execute(userSelectStatement, :arguments => [params[:lastname]])
if result.size < 1
halt(404)
end
result.first.to_json
rescue Exception => e
log.error 'Error in select a user'
log.error(e)
halt(404)
end
end
userSelectStatement = session.prepare("SELECT firstname,lastname, age, email, city
FROM users where lastname = ?")
…
43. Select user with GET
43
get '/users/:lastname' do
begin
result = session.execute(userSelectStatement, :arguments => [params[:lastname]])
if result.size < 1
halt(404)
end
result.first.to_json
rescue Exception => e
log.error 'Error in select a user'
log.error(e)
halt(404)
end
end
userSelectStatement = session.prepare("SELECT firstname,lastname, age, email, city
FROM users where lastname = ?")
…
44. Quick note on Async
• Generates a Future
• Non-blocking until get
44
future = session.execute_async(statement)
# register success listener
future.on_success do |rows|
rows.each do |row|
puts "#{row["artist"]}: #{row["title"]} / #{row["album"]}"
end
end
45. Quick note on Async
• Generates a Future
• Non-blocking until get
45
future = session.execute_async(statement)
# register success listener
future.on_success do |rows|
rows.each do |row|
puts "#{row["artist"]}: #{row["title"]} / #{row["album"]}"
end
end
46. Quick note on Async
• Generates a Future
• Non-blocking until get
46
future = session.execute_async(statement)
# register success listener
future.on_success do |rows|
rows.each do |row|
puts "#{row["artist"]}: #{row["title"]} / #{row["album"]}"
end
end
47. Update a user with PUT
47
put '/users' do
begin
session.execute(userUpdateStatement, :arguments => [params[:age].to_i, params[:lastname]])
"Updated"
rescue Exception => e
log.error 'Error in update a user'
log.error(e)
halt(404)
end
end
userUpdateStatement = session.prepare("UPDATE users SET age = ? WHERE lastname = ?")
48. Remove a user with DELETE
48
delete '/users/:lastname' do
begin
session.execute(userDeleteStatement, :arguments => [params[:lastname]])
"Deleted"
rescue Exception => e
log.error 'Error in delete a user'
log.error(e)
halt(404)
end
end
userDeleteStatement = session.prepare("DELETE FROM users WHERE lastname = ?")