Apollo is a Cassandra object modeling for node.js
##Notes Apollo is in early develeopment stage. Code and documentation are incomplete!
##Installation
npm install apollo
##Usage
Include Apollo and start creating your models
var Apollo = require('apollo-cassandra');
var connection = {
"hosts": [
"127.0.0.1"
],
"keyspace": "my_keyspace"
};
var apollo = new Apollo(connection);
apollo.connect(function(err){
if(err) throw err;
/* do amazing things! */
})
Apollo
constructor takes two arguments: connection
and options
. Let's see what they are in depth:
-
connection
are a set of options for your connection and accept the following parameters:hosts
is an array of string written in the formhost:port
or simplyhost
assuming that the default port is 9042keyspace
is the keyspace you want to use. If it doesn't exist apollo will create it for youusername
andpassword
are used for authentication- Any other parameter is defined in api
-
options
are a set of generic options. Accept the following parameters:replication_strategy
can be an object or a string representing cassandra replication strategy. Default is{'class' : 'SimpleStrategy', 'replication_factor' : 1 }
Here is a complete example:
var apollo = new Apollo(
{
hosts: ['1.2.3.4', '12.3.6.5', 'cassandra.me.com:1212'],
keyspace: 'mykeyspace',
username: 'username',
password: 'password'
},
{
replication_strategy: {'class' : 'NetworkTopologyStrategy', 'dc1': 2 }
}
);
Now that apollo is connected, create a model
describing it through a schema
var PersonSchema = {
{
fields:{
name : "text",
surname : "text",
age : "int"
},
key:["name"]
}
};
Now create a new Model based on your schema. The function add_model
uses the table name and schema as parameters.
var Person = apollo.add_model('person',PersonSchema);
From your model you can query cassandra or save your instances
/*Quesry your db*/
Person.find({name: 'jhon'}, function(err, people){
if(err) throw err;
console.log('Found ', people);
});
/*Save your instances*/
var alex = new Person({name: "Alex", surname: "Rubiks", age: 32});
alex.save(function(err){
if(!err)
console.log('Yuppiie!');
});
A schema can be a complex object. Take a look at this example
PersonSchema = {
"fields": {
"id" : { "type": "uuid", "default": {"$db_function": "uuid()"} },
"name" : { "type": "varchar", "default": "no name provided"},
"surname" : { "type": "varchar", "default": "no surname provided"},
"complete_name" : { "type": "varchar", "default": function(){ return this.name + ' ' + this.surname;}},
"age" : { "type": "int" },
"created" : {"type": "timestamp", "default" : {"$db_function": "now()"} }
},
"key" : [["id"],"created"],
"indexes": ["name"]
}
What does the above code means?
fields
are the columns of your table. For each column name the value can be the type or an object containing more specific informations. i.e."id" : { "type": "uuid", "default": {"$db_function": "uuid()"} },
in this example id type isuuid
and the default value is a cassandra function (so it will be executed from the database)."name" : { "type": "varchar", "default": "no name provided"},
in this case name is a varchar and, if no value will be provided, it will have a default value ofno name provided
. The same goes forsurname
complete_name
the default values is calculated from others field. When apollo processes you model instances, thecomplete_name
will be the result of the function you defined. In the functionthis
is the current model instance.age
no default is provided and we could write it just as"age": "int"
created
, like uuid(), will be evalueted from cassandra using thenow()
function
key
: here is where you define the key of your table. As you can imagine, the first value of the array is thepartition key
and the others are theclustering keys
. Thepartition key
can be an array defining acompound key
. Read more about keys on the documentationindexes
are the index of your table. It's always an array of field names. You can read more on the documentation
A model is an object representing your cassandra table
. Your application interact with cassandra through your models. An instance of the model represents a row
of your table.
Let's create our first model
var Person = apollo.add_model('person',PersonSchema);
now instantiate a person
var john = new Person({name: "John", surname: "Doe"});
When you instantiate a model, every field you defined in schema is automatically a property of your instances. So, you can write:
john.age = 25;
console.log(john.name); //John
console.log(john.complete_name); // undefined.
note: john.complete_name
is undefined in the newly created instance but will be populated when the instance is saved because it has a default value in schema definition
John is a well defined person but he is not still persisted on cassandra. To persist it we need to save it. So simple:
john.save(function(err){
if(err)
return 'Houston we have a problem';
else
return 'all ok, saved :)';
});
When you save an instance all internal validators will check you provided correct values and finally will try to save the instance on cassandra.
Ok, we are done with John, let's delete it:
john.delete(function(err){
//...
});
ok, goodbye John.
Your model could have some fields which are not saved on database. You can define them as virtual
PersonSchema = {
"fields": {
"id" : { "type": "uuid", "default": {"$db_function": "uuid()"} },
"name" : { "type": "varchar", "default": "no name provided"},
"surname" : { "type": "varchar", "default": "no surname provided"},
""
"complete_name" : {
"type": "varchar",
"virtual" : {
get: function(){return this.name + ' ' +this.surname;},
set: function(value){
value = value.split(' ');
this.name = value[0];
this.surname = value[1];
}
}
},
}
}
A virtual field is simply defined adding a virtual
key in field description. Virtuals can have a get
and a set
function, both optional (you should define at least one of them!).
this
inside get and set functions is bound to current instance of your model.
Every time you set a property for an instance of your model, internal type validator check that the value is valid. If not an error is thrown. But how to add a custom validator? You need to provide your custom validator in the schema definition. For example, if you want to check age to be a number greater than zero:
var PersonSchema = {
//... other properties hidden for clarity
age: {
type : "int",
rule : function(value){ return value > 0; }
}
}
your validator must return a boolean. If someone will try to assign jhon.age = -15;
an error will be thrown.
You can also provide a message for validation error in this way
var PersonSchema = {
//... other properties hidden for clarity
age: {
type : "int",
rule : {
validator : function(value){ return value > 0; },
message : 'Age must be greater than 0'
}
}
}
Than the error will have your message. Message can also be a function; in that case it must return a string:
var PersonSchema = {
//... other properties hidden for clarity
age: {
type : "int",
rule : {
validator : function(value){ return value > 0; },
message : function(value){ return 'Age must be greater than 0. You provided '+ value; }
}
}
}
The error message will be Age must be greater than 0. You provided -15
Ok, now you have a bunch of people on db. How to retrieve them?
Person.find({name: 'John'}, function(err, people){
if(err) throw err;
console.log('Found ', people);
});
In the above example it will perform the query SELECT * FROM person WHERE name='john'
but find()
allows you to perform even more complex queries on cassandra. You should be aware of how to query cassandra. Every error will be reported to you in the err
argument, while in people
you'll find instances of Person
. If you don't want apollo to cast results in instances of your model you can use the raw
option as in the following example
Person.find({name: 'John'}, { raw: true }, function(err, people){
//people is an array of plain objects
});
Let's see a complex query
var query = {
name: 'John', // stays f
or name='john'
age : { '$gt':10 }, // stays for age>10 You can also use $gte, $lt, $lte
surname : { '$in': ['Doe','Smith'] }, //This is an IN clause
$orderby:{'$asc' :'age'} }, //Order results by age in ascending order. Also allowed $desc and complex order like $orderby:{'$asc' : ['k1','k2'] } }
$limit: 10 //limit result set
}
Note that all query clauses must be Cassandra compliant. You cannot, for example, use $in operator for a key which is not the partition key. Querying in Cassandra is very basic but could be confusing at first. Take a look to this post and, obvsiouly, to the documentation
Complete api definition is available on 3logic website.
Anyway you can generate documentation cloning this project and launching grunt doc
To test Apollo create a file named local_conf.json
in test
directory with your connection configuration as below
{
"contactPoints": [
"127.0.0.1",
"192.168.100.65",
"my.cassandra.com:9845"
],
"keyspace": "tests"
}
Apollo is brought to you by
Thanks to Gianni Cossu and Massimiliano Atzori for helping.
Thanks to 3logic too!