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

 

Thank you for your interest!

We will contact you as soon as possible.

Send us a message

Oops, something went wrong
Please try again or contact us by email at info@tikalk.com