Friday, December 6, 2013

Simplifying REST with MongoDB

REST and MongoDB seem to be a match made in heaven. JSON is the popular format for transferring data across REST interfaces using the omnipresent and easy to use HTTP. BSON the data storage format of MongoDB is also a superset of JSON and is capable of accepting JSON input.

The natural expectation, then, is how can I use JSON in REST to directly store and retrieve data into MongoDB.

MongoDB's mongod, when started with the --rest option provides a bare minimum REST interface for querying MongoDB, using prototype URL of format http://127.0.0.1:28017/mydatabase/mycollection/

More information can be found at http://docs.mongodb.org/ecosystem/tools/http-interfaces/ under heading Simple REST Interface

But, for REST based insert, update, upsert and delete, you need to front Mongo DB with one of the following third party servers:

REST Interfaces
DrowsyDromedary is a REST layer for MongoDB based on Ruby.
MongoDB Rest is an alpha REST interface to MongoDB that uses the MongoDB Node Native driver.
Mongodb Java REST server based on Jetty.
Kule is a customizable REST interface for MongoDB based on Python.

HTTP Interfaces
Sleepy Mongoose (Python)
Sleepy Mongoose is a full featured HTTP interface for MongoDB.

Often, using these upfront REST to mongo translators, means intoducing another moving part into your architecture and having to contend with issues like cross origin access. Also there is the requirement to customize the REST to MongoDB translation. Hence many prefer to write their own simple REST controllers which delegate calls to MongoDB.

Well, so did I and gradually realized that, the code can be simplified quite a bit.

With the assumption that you have conventional java based REST controllers set up in your web application using say Jersey, here is the controller class you would have to write:


import javax.ws.rs.Path;

@Path("vehicles")
public class VehicleController extends MongoBaseController{
    @Override
    public String getEntityName() {
     return "vehicles";
    }

    @Override
    public String getKey() {
     return "vehicleId";
    }
}





Writing just this simple code, you can get, the following CRUD REST APIs, storing into your MongoDB database, since it will expose the following services:

at URL http://localhost:10080/<My Web App>/restful/vehicles
Http PUT for inserting vehicles in MongoDB
Http POST for updating and upserting vehicles in MongoDB
Http DELETE for deleting vehiles in MongoDB

The critical functionality as you might have guessed lies nicelt encapsulated in the base class MongoBaseController, which can to common to any number of entities that we want to CRUD from REST to MongoDB
package com.ghag.rnd.rest;


import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
import javax.ws.rs.POST;
import javax.ws.rs.PUT;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;

import com.ghag.mongodb.MongoCrudService;
import com.mongodb.BasicDBObject;
import com.mongodb.DBCollection;
import com.mongodb.DBObject;
import com.mongodb.util.JSON;
 

public abstract class MongoBaseController {
    
    abstract public String getEntityName();
    abstract public String getKey(); 

    @PUT
    @Produces(MediaType.APPLICATION_JSON)
    @Consumes(MediaType.APPLICATION_JSON)
    public String insert(String entity){
     DBCollection coll = MongoCrudService.getConnection().getCollection(getEntityName());
     BasicDBObject row = (BasicDBObject)JSON.parse(entity);
  coll.insert(row);
  
  System.out.println("inserted entity "+row);
     return "SUCCESS_INSERT";
    }
    
    @POST
    @Produces(MediaType.APPLICATION_JSON)
    @Consumes(MediaType.APPLICATION_JSON)
    public String upsert(String entity){
     
     DBObject inputRow = (DBObject)JSON.parse(entity);
     DBCollection coll = MongoCrudService.getConnection().getCollection(getEntityName());
     
     DBObject src = coll.findOne(new BasicDBObject(getKey(), inputRow.get(getKey())));
     if(src == null)
      src = inputRow;
     
  coll.update(src, inputRow, true, false);
  
  System.out.println("upserted entity "+inputRow);
     return "SUCCESS_UPDATE";
    }
    
    @DELETE
    @Produces("text/json")
    @Consumes(MediaType.APPLICATION_JSON)
    public String delete(String entity){
  DBCollection coll = MongoCrudService.getConnection().getCollection(getEntityName());
  DBObject inputRow = (DBObject)JSON.parse(entity);
  DBObject src = coll.findOne(new BasicDBObject(getKey(), inputRow.get(getKey())));
  coll.remove(src);
  
  System.out.println("removed entity "+src);
     return "SUCCESS_DELETE";
    }
    
    
}







So now, if you want to persist another entity say Book via REST and into MongoDB all you would need to write is a small class similar to





@Path("books")
public class BookController extends MongoBaseController{
    @Override
    public String getEntityName() {
     return "books";
    }

    @Override
    public String getKey() {
     return "bookId";
    }
}


Thats it!

The new controller itself is free from any details of the structure of the Book entity, like its fields, data types etc.

That is truely combining the power of REST and MongoDB.

Mucho Eleganto! dont you think?

Cheers!


No comments: