MongoDB , Spring and REST – Trio for JEE Dynamic Data Access.

In the last couple of weeks, I've been working on building a new architecture of a product for one of our customers. As many other products you probably know, I had to design the object model and be able to persist it to the the DB. So natively, I wanted to use JPA and Hibernate in order to persist the objects into the RDBMS. But then a new requirement came and bumped my face  - “parts of the domain-model and the data-model may be changed dynamically at runtime every now and again, while the system should stay running and without downtime”. The changes for the model can be applied by end users with “admin” role of the product, actually without changing the product itself (no new revision the product should be involved in this process).
The dynamic part described above is NOT a small data set: it can contain a few Gigabytes of data. The potential changes of the model include everything you can think of – adding new types, changing existing types and their fields, changing the field types and constraints, adding/changing/removing relationships between existing types , removing types etc.
In addition to "create", "update" and "delete" operations on the data, the end user has a dynamic window that can query and filter for dynamic types which had been defined. This filter window includes expression builder, paging ordering etc.
So, while part of the application will remain classic, which I can harness all the classic solution including GeneicDAO for various static entities that uses JPA/Hibernate to persist them, the other part is dynamic , and may need an alternative solution. The challenge with the dynamic part of the model, is both on the software and the storage. There were various alternatives , including Hibernate dynamic model, changing code at runtime, dynamic generation of SQL queries and more. I will NOT detail them here, but you can just taste them in this thread.

Enter MongoDB

I chose another option - MongoDB. MongoDB belongs to the NoSQL new databases which are NOT RDBMS.  More specifically, is a Document DB. Unlike a relational database management system, MongoDB manages collections of JSON-like documents. This allows many applications to model data in a more natural way, as data can be nested in complex hierarchies and still be query-able and indexable. In addition, MongoDB support Ad-Hoc queries which meets perfectly my requirement for the filtering window dicussed above. Unlike many other non-relational database solutions, any field can be queried at any time. MongoDB supports range if queries, regular expression searches, and other special types of queries in addition to exactly matching fields. So, if for example I have the following document in the MongoDB:

{
  "username" : "bob",
  "address" : {
    "street" : "123 Main Street",
    "city" : "Springfield",
    "state" : "NY"
  }
}

We can query for this document (and all documents with an address in New York) with:
 

db.users.find({"address.state" : "NY"})

Building the Architecture

The new product will be Spring based JEE application and provide REST interface for its services with JAS-RS and JSON mime type. As said, it uses GenericDAO as its strongly type interface for the  entities , implemented with JPA/Hibernate. So, I wanted to add yet another DAO called "DynamicModelDao" interface that use Stringed JSON data. This data will be persisted and fetched to and from the DB without going through any defined class. No Domain Driven Design here. No rigid schema. No hard coded columns and tables and no classes and field. This will fullfill the requirements for being dynamic , and enable the end user to change the domain without affecting the software and and the DB underneath it.

So without much more words lets go to the “real” stuff and see how to integrate MongoDB within our RESTful Spring application and GenericDAO data access. First lets define the interfaces. The DAO interface is “DynamicModelDao” and just extends the GenericDAO interface. Note  that neither GenericDAO nor DynamicModelDao reveals the the underlying implementation of the data access, so we can move to yet another storage to persist our Stringed information. More than that, both static and dynamic models share the same GenericDAO interface to persist and fetch the data of the application with JPA and MongoDB implementations.

Here GenericDao interfaces code :

 

public interface GenericDao  <T, ID extends Serializable> {
	void create(T entity);
	void create(T entity,ID id);
	
	T findById(ID id);
	void update(T entity);
	void delete(T entity);
	ResultPage<T> findByFilter(String q,String orders, int pageNumber,int pageSize);

}

 

All the methods are self explained except the last one. The last method enables the client to filter results by a query string, defines the order , page number and size. It return a page of the results using the ResultPage. Here is the  ResultPage DTO which holds both the results and count of total results:

@XmlRootElement
public class ResultPage<T> {

	private List<T> results = new LinkedList<T>();

	private long count;

	public ResultPage() {
	}

	public ResultPage(List<T> results, long count) {
		this.results = results;
		this.count = count;
	}

	@XmlElement
	public List<T> getResults() {
		return results;
	}

	public void setResults(List<T> results) {
		this.results = results;
	}

	@XmlElement
	public long getCount() {
		return count;
	}

	public void setCount(long count) {
		this.count = count;
	}

	@Override
	public String toString() {
		return "ResultPage [results=" + results + ", count=" + count + "]";
	}
}

The  DynamicModelDao just need to extend the GenericDAO interface and mark String as its generic type to be persisted:

public interface DynamicModelDao extends GenericDao<String, Serializable>{
}

 

Now, lets implement the  DynamicModelDao interlace with MongoCollectionDao which uses String as the type parameter to be persisted and fetched from the DB.

import java.io.Serializable;
import java.util.LinkedList;
import java.util.List;

import javax.annotation.PostConstruct;

import "com.mycompany.myproduct.persistence.face.DynamicModelDao;
import "com.mycompany.myproduct.persistence.face.pagination.ResultPage;
import com.mongodb.DB;
import com.mongodb.DBCollection;
import com.mongodb.DBCursor;
import com.mongodb.DBObject;
import com.mongodb.util.JSON;
public  class MongoCollectionDao implements DynamicModelDao {
	private DB db;
	private String collectionName;
	private DBCollection dbCollection;

	public void setDb(DB db) {
		this.db = db;
	}

	public void setCollectionName(String collectionName) {
		this.collectionName = collectionName;
	}
	
	@PostConstruct
	public void init(){
		dbCollection = db.getCollection(collectionName);
	}

	@Override
	public void create(String entity) {
		DBObject dbObject = getDbObject(entity);
		dbCollection.insert(dbObject);
	}

	private DBObject getDbObject(String entity) {
		DBObject dbObject = (DBObject) JSON.parse(entity);
		return dbObject;
	}
	
	@Override
	public void create(String entity,Serializable id) {
		DBObject dbObject = getDbObject(entity);
		dbObject.put("_id", id);
		dbCollection.insert(dbObject);
	}
	
	@Override
	public void update(String entity) {
		DBObject dbObject = getDbObject(entity);
		dbCollection.save(dbObject);
	}

	@Override
	public String findById(Serializable id) {
		DBObject dbObject= dbCollection.findOne(id);
		return JSON.serialize(dbObject);
	}

	@Override
	public void delete(String entity) {
		DBObject dbObject = getDbObject(entity);
		dbCollection.remove(dbObject);
	}

	public ResultPage findByFilter(String q,String orders, int pageNumber,int pageSize) {
		List<String> results = new LinkedList<String>();
		long count;
		DBCursor cursor;
		if(q==null){
			count = dbCollection.count();
			cursor = dbCollection.find();
		}
		else{
			DBObject queryDBObject = getDbObject(q);
			count = dbCollection.count(queryDBObject);
			cursor = dbCollection.find(queryDBObject);		
		}
		if(orders != null)
			cursor.sort(getDbObject(orders));
		cursor.skip(pageSize*(pageNumber-1));
		cursor.limit(pageSize);
		while(cursor.hasNext())
            results.add(cursor.next().toString());
        return new ResultPage(results,count);
	}


}

 

 

If you look at the code above you can see that the main interface to access MongoDB is the DB class. The DBCollection represent the collection of the document being accessed by the DAO. In order to create the DB class, we need to define a Spring FactoryBean and inject it into our DAO:

	<bean id="mongo" class="com.mongodb.Mongo">
		<constructor-arg value="localhost" />
		<constructor-arg value="27017" />
	</bean>
	
	<bean id="db" class="com.mycompany.myproduct.mongodb.infra.DbFactoryBean">
		<property name="mongo" ref="mongo" />
		<property name="name" value="my-db-name" />
	</bean>
	
	
	<bean id="dynamicModelDao" class="com.mycompany.myproduct.persistence.mongodb.MongoCollectionDao">
		<property name="db" ref="db" />
		<property name="collectionName" value="myCollectionName" />
	</bean>

 

Now all we are left is create our Service layer interface and implementations. Then we need to externalize it with REST using JAXRS:

@Path("/dyanmic-entities")
public interface DynamicEntityService {

	@PUT
	@Path("/{id}")
	@Consumes(MediaType.APPLICATION_JSON)
	void createEntity(@PathParam("id")String id,String data);

	@POST
	@Consumes(MediaType.APPLICATION_JSON)
	void createEntity(String data);

	@POST
	@Path("/{id}")
	@Consumes(MediaType.APPLICATION_JSON)
	void updateEntity(@PathParam("id")String id,String data);
	
	@DELETE
	@Path("/{id}")
	@Consumes(MediaType.APPLICATION_JSON)
	void removeEntity(@PathParam("id")String id);
	
	
	@GET
	@Produces(MediaType.APPLICATION_JSON)
	@Path("/{id}")
	String findEntityById(@PathParam("id")String id);
	
	@GET
	@Produces(MediaType.APPLICATION_JSON)
 	ResultPage<String> findEntitysByFilter(	@QueryParam("q") String q, 
 											@QueryParam("orders") String orders, 
 											@QueryParam("pageNumber") @DefaultValue("1") int pageNumber,
 											@QueryParam("pageSize") @DefaultValue("10")int pageSize);

}

And now, to the implementation, which currently delegates the work to the DAO, but in the future will probably have some more logic:

public class DymaicEntityServiceImpl implements DymanicEntityService {
	private DynamicModelDao dynamicModelDao;

	public void setDynamicModelDao(DynamicModelDao dynamicModelDao) {
		this.dynamicModelDao = dynamicModelDao;
	}

	@Override
	public void createEntity(String data) {
		String id = UUID.randomUUID().toString();
		dynamicModelDao.create(data, id);
	}

	@Override
	public void createEntity(String id, String data) {
		dynamicModelDao.create(data, id);
	}

	@Override
	public void updateEntity(String id, String data) {
		dynamicModelDao.update(data);
	}

	@Override
	public void removeEntity(String id) {
		dynamicModelDao.delete(id);
	}

	@Override
	public String findEntityById(String id) {
		return dynamicModelDao.findById(id);
	}

	@Override
	public ResultPage findEntitysByFilter(String q, String orders,
			int pageNumber, int pageSize) {
		return dynamicModelDao.findByFilter(q, orders, pageNumber, pageSize);
	}

}

 

Testing the New Product

That's all - after deploy this application in any web server, you can test a a running service. For example to persist a new dynamic entity instance we can use the “POST” a user data:
 

echo '{"name":"Scott", age :38}' | curl -X POST -H 'Content-type: application/json; charset=UTF-8' -d @- http://localhost/dyanmic-entities

and we can query first page of users with name “Scott”
 

http://localhost/dyanmic-entities?q=%7B%22name%22%3A%22Scott%22%7D

or we can load second page (each page 5 users) of users ordered by id:
 

http://localhost/dyanmic-entities?orders=%7B%22_id%22%3A%221%22%7D&pageSize=5&pageNumber=2

 

Comments

great read, thanks!

really interesting concept!
how did you solve the dynamic GUI problem? As you store JSON, is it a Java Script Web GUI? Does it reflect changes in the model automatically (if yes, how) or has one to adopt the GUI somehow?
i am really curious!
greets

Thanks. We haven't solved the dynamic GUI problem yet... However we plan to do it.

We plan to develop an application that will enable the administrator/customer-service to define the dynamic model part for the GUI. The definistions can be saced and used for generating dynamic screens. Naturally, there should be links to the dynamc model created in MongoDB.

Any chance you could provide the source to this project? What IDE do you use? I am looking to implement a similar project.

 

Thanks for sharing this post.

 The project is a commercial closed source, and the code above was developed as an integral part of it, with all right reserved to the customer. Nevertheless, please feel free to use the codes above as a reference, and if you have any questions with regard it, I'll be happy to assist. I use Eclipse as the IDE, but of course you can use your own preferred IDE.






 

 

 The project is a commercial closed source, and the code above was developed as an integral part of it, with all right reserved to the customer. Nevertheless, please feel free to use the codes above as a reference, and if you have any questions with regard it, I'll be happy to assist. I use Eclipse as the IDE, but of course you can use your own preferred IDE.






 

 

Nice reading. I actually came up with a similar idea a year or so ago, and built a metamodel driven cms (based on SmartGWT + Spring framework + CouchDB + Lucene). It has a very neat meta model administration interface, along with a fully dynamic admin area. I chose CouchDB for storage back then, it's somewhat similar to MongoDB from the usage perspective, but started looking closely at MongoDB after I discovered the spring-data project.

 

One thing to note though, I experienced certain performance issues with MongoDB Java driver - querying 100 docs by ID in a 100k doc set was taking around 100ms, while CouchDB was way faster in the same scenario. I suspect that BSON converter is the culprit, but I'll have to confirm that once I get some space to breathe :)

Thanks for sharing your experience.

Regarding CouchDB - The "killer feature" that was missing for this product in CouchDB is "Dynamic queries". CouchDB doesn’t allow running dynamic queries against the store - You can define views with the help of Javascript-based mapreduce functions, however, in my opinion, its less declarative and less usable compare to MongoDB’s query language. The next major version of CouchDB (now in developer preview stage) is going to add this functionality with UnQL, but that might be too late for us…

 

Regarding the benchmark – Personally, I think that its better to take “real” use cases from the product’s requirements, and test them against the store (rather than using “general” benchmark). In our case, the POC for our use cases, produced great throughput – a way beyond our needs, so we were quite happy with it.