Within the last few months I've finally started using NoSQL databases in some of my projects. I started with db4o, then refactored my code to use RavenDB. Part of the learning curve, that was tough for me, was the idea that a domain model would change based on the chosen persistence layer (db4o, Raven, etc...). That didn't sit well with me. While it can be a fine solution, it's just not for me.
Based on a few months of using these tools, I demonstrate here how I was able to keep my domain model relatively pure and still use RavenDB. What I mean by "pure" is that I don't want traces of the persistence layer manifesting themselves in the domain model. And I want properties, that are natural for a class, to exist in that class. For example, a Team class should have a List<Player> property, not just a list of Player IDs, or parts of the Player class represented again in the Team class.
For this post, we'll be looking at Presto (http://presto.codeplex.com/) source code, and how it kept a somewhat pure domain model while using RavenDB. We'll be working with these two domain classes: Application and CustomVariableGroup. An Application is simply an app, such as an app to store music, or an app to record employee information for your company. A CustomVariableGroup is simply a container for CustomVariables. Defining a CustomVariable is unnecessary here. It's simply a POCO, used as a property on an Application class.
Let's start by showing how we create a document store. Here is the actual property:
And here is the method that sets it:
Now let's show the two domain classes. Here is Application:
The property that has the comment "// For RavenDB" after it, exists so that we know the IDs of other entities that belong to this entity. This is really the entire point of what we're doing here. We store the IDs of the entities within this class, so they can be populated from a RavenDB call when it's time to retrieve our entities from the DB. It is the responsibility of the Raven data layer to manage this.
This is absolutely Raven-specific. We could have defined a partial class to hold this Raven-specific property, and kept this class file completely pure, but I didn't want to take it that far.
Also notice that we have a property CustomVariableGroups. This is what I mean about maintaining a pure domain model. With RavenDB, there are some guidelines that, since it's a document database, we don't store full documents/entities in another entity. Instead, you store only some properties of other entities in your current entity. But, I'm getting ahead of myself here.
Also notice the JsonIgnore attribute on the CustomVariableGroups property. RavenDB will ignore this property when saving to the database. Raven, like db4o, will persist POCO properties. However, unlike db4o, that persisted property is frozen in state, and the *instance* of that property belongs to that class. For example, if changes are made to a CustomVariableGroup in the DB, those changes *will not* be reflected when retrieving our class. This is why we store the IDs; so that we can get the latest version of our property, even if it has been changed by someone, or something, else.
Let's show our CustomVariableGroup class:
To finish showing our entities, let's show the base class. This *is* Raven-specific, but it's a base class for exactly that purpose, and the derived classes don't show this information in their class files.
The Etag property is out of the scope of this blog.
If you've been able to stay with me so far, we're almost done. Now we need to show how the Raven data layer manages these properties.
Here is how we save an Application:
What happens here is that we loop through all of the CustomVariableGroups in the Application, and add their IDs to the CustomVariableGroupIds property. When the entity gets saved, the IDs will get saved, and not the actual POCOs. This way, when we load an Application from the DB, we can retrieve the latest instance of the POCOs by the IDs:
The important part here is the Include() line. That instructs RavenDB to retrieve, in the same session, the CustomVariableGroups with those IDs. Because we've done that, the code to call HydrateApplication can then add the appropriate CustomVariableGroups to each Application object, all on the same session:
That's it. Now that that's done, each Application has the latest CustomVariableGroup associated with it. Those CustomVariableGroups can change over time, yet we still get the latest, and correct, one using this approach.