Using Java to work with Versioned Data
A few days ago I wrote about how to structure version details in MongoDB. In this and subsequent articles I’m going to present a Java based approach to working with that revision data.
I have published all this work as an open source repository on github. Feel free to fork it:
https://github.com/dwatrous/mongodb-revision-objects
Design Decisions
To begin with, here are a few design rules that should direct the implementation:
- Program to interfaces. Choice of datastore or other technologies should not be visible in application code
- Application code should never deal with versioned objects. It should only deal with domain objects
Starting with A above, I came up with a design involving only five interfaces. The management of the Person class is managed using VersionedPerson, Person, HistoricalPerson and PersonDAO. A fifth interface, DisplayMode, is used to facilitate display of the correct versioned data in the application. Here’s what the Person interface looks like:
public interface Person { PersonName getName(); void setName(PersonName name); Integer getAge(); void setAge(Integer age); String getEmail(); void setEmail(String email); boolean isHappy(); void setHappy(boolean happy); public interface PersonName { String getFirstName(); void setFirstName(String firstName); String getLastName(); void setLastName(String lastName); } } |
Note that there is no indication of any datastore related artifacts, such as an ID attribute. It also does not include any specifics about versioning, like historical meta data. This is a clean interface that should be used throughout the application code anywhere a Person is needed.
During implementation you’ll see that using a dependency injection framework makes it easy to write application code against this interface and provide any implementation at run time.
Versioning
Obviously it’s necessary to deal with the versioning somewhere in the code. The question is where and how. According to point B above, I want to conceal any hint of the versioned structure from application code. To illustrate, let’s imagine a bit of code that would retrieve and display a person’s name and email.
First I show you what you want to avoid (i.e. DO NOT DO THIS).
Person personToDisplay; VersionedPerson versionedPerson = personDao.getPersonByName(personName); if (displayMode.isPreviewModeActive()) { personToDisplay = versionedPerson.getDraft(); } else { personToDisplay = versionedPerson.getPublished(); } System.out.println(personToDisplay.getName().getFirstName()); System.out.println(personToDisplay.getEmail()); |
There are a few problems with this approach that might not be obvious based on this simple example. One is that by allowing the PersonDAO to return a VersionedPerson, it becomes necessary to include conditional code everyehere in your application that you want to access a Person object. Imagine how costly a simple change to DisplayMode could be over time, not to mention the chance of bugs creeping in.
Another problem is that your application, which deals with Person objects, now has code throughout that introduces concepts of VersionedPerson, HistoricalPerson, etc.
In the end, all of those details relate to data access. In other words, your Data Access Object needs to be aware of these details, but the rest of your application does not. By moving all these details into your DAO, you can rewrite the above example to look like this.
Person personToDisplay = personDao.getPersonByName(personName); System.out.println(personToDisplay.getName().getFirstName()); System.out.println(personToDisplay.getEmail()); |
As you can see, this keeps your application code much cleaner. The DAO has the responsibility to determine which Person object to return.
DAO design
Let’s have a closer look at the DAO. Here’s the PersonDAO interface:
public interface PersonDAO { void save(Person person); void saveDraft(Person person); void publish(Person person); Person getPersonByName(PersonName name); Person getPersonByName(PersonName name, Integer historyMarker); List<Person> getPersonsByLastName(String lastName); } |
Notice that the DAO only ever receives or returns Person objects and search parameters. At the interface level, there is no indication of an underlying datastore or other technology. There is also no indication of any versioning. This encourages application developers to keep application code clean.
Despite this clean interface, there are some complexities. Based on the structure of the mongodb document, which stores published, draft and history as nested documents in a single document, there is only one ObjectID that identifies all versions of the Person. That means that the ObjectID exists at the VersionedPerson level, not the Person level. That makes it necessary to pass some information around with the Person that will identify the VersionedPerson for write operations. This comes through in the implementation of the MorphiaPersonDAO.
Download
You can clone or download the mongodb-revision-objects code and dig in to the details yourself on github.