I use MongoDB in production code with the Repository Pattern for over 2 years now and I can say that it really helped me over time. The abstractions serve well for testing (in-memory) and production (MongoDB).
I use Java and the code looks something like this (roughly):
public interface MyStorage {
boolean add(MyDoc doc);
boolean update(MyDoc doc);
boolean remove(String docId);
boolean commit();
MyDoc get(String docId);
MyStorageQuery newQuery();
List<MyDoc> query(MyStorageQuery q);
}
I have a factory for each storage implementation which creates new instances of the MyDoc object.
I interchange the implementation between MongoDb and my own hand-rolled mock for testing.
The MongoDB implementation uses a MyDoc class which extends the BasicDBObject like so:
public interface MyDoc {
Data getData(); // let's assume this is a nested doc
void setData(Data d);
String getId();
long getTimestamp();
void setTimestamp(long time);
}
MongoDbMyDoc extends BasicDBObject implements MyDoc {
MongoDbObject() { }
void setId(String id) {
this.put("_id", id);
}
String getId() {
return super.get("_id");
}
void setData(Data d) {
dataObj = new BasicDBObject();
dataObj.put("someField",d.someField);
dataObj.put("someField2", d.someField2);
super.put("data",dataObj);
}
...
}
I then in the actual storage implementation use the MongoDB Java client to return instances of my implementation from the DB. Here is the constructor for my MongoDB storage implementation:
public MongoDbMyStorage(DB db, String collection) {
//DB in a mongodb object (from the client library) which was instantiated elsewhere
dbCollection = db.getCollection(collection);
dbCollection.setObjectClass(MongoDbMyDoc.class);
this.factory = new MongoDbMyDocFactory();
}
There are 2 more interfaces here:
MyStorageQuery
which is also implemented as a BasicDBObject for the MongoDB implementation and generated using the newQuery()
of the storage interface.
And MyDocFactory
which isn't presented here, but it is basically a document factory that knows what the storage implementation is and generates the MyDoc
instances accordingly.
Caveats:
One thing where the abstraction doesn't make much sense is in defining the indexes used by the MongoDB storage. I put all my ensureIndex(...)
calls in the constructor, not very generic, but defining indexes per collection is a MongoDB specific optimization so I can live with it.
Another is that commit is implemented using the getLastError()
command which from my experience didn't work so well. It isn't a problem for me since I almost never explicitly commit a change.