I’ve been meaning to put together a reference application for using .NET and Couchbase Server.  While traveling to London for the Progressive NoSQL conference at Skills Matter, I passed the hours waiting for my hotel room to be ready by writing some code at a Startbucks for an application I call TapMap.

The app is pretty straightforward; it’s really just a Twitter knockoff with a particular focus on beers.  The basic idea is that someone at a bar will report having found (and probably drunk) a particular brew at a particular place.  That report is called a “tap.”  The “map” feature renders a series of taps on a Nokia Map, using GeoCouch.  The value is that users can quickly learn where to find their favorite brews!

The code is still very alpha and my HTML skills need to be upgraded.  But I’ve implemented enough that I am going to start a series of posts on the code patterns and solutions exposed by TapMap.  This post will be part one and will describe how to create a simple Repository of T for Couchbase using the .NET Client Library.  You can get the code at http://github.com/couchbaselabs/TapMap.

If you’re not familiar with repositories, the basic idea is that you have a class that is responsible for the data access of a domain object.  So to query for a user by his or her email address, you would use the GetByEmail(string email) method of the UserRepository.  Repositories also typically contain standard standard CRUD methods.  There’s a bit more to the pattern, but I’ll demonstrate its basic usage.  Read more at http://msdn.microsoft.com/en-us/library/ff649690.aspx.

Inheritance is a natural way to achieve code reuse.  TapMap uses a base repository class to provide CRUD methods to its subclasses.  Additionally, the base class makes use of generics so that CRUD methods may return instances of T instead of just objects.  Constraining T to a particular type also allows for some additional benefits that I’ll discuss shortly.

public abstract class RepositoryBase<T> where T : ModelBase
{
//crud methods here
}

As you may already know, Couchbase Server 2.0 does not impose a schema on its documents.  You store a JSON document with any properties you wish to include.  That said, all documents are stored with a reserved “_id” property that derives its value from its key.

Adding a document as follows:

client.Store(StoreMode.Add, “beer_Three_Philosophers”, “{ Name : Three Philosophers }”);

Results in a document like the following:

{
“_id” : “beer_Three_Philosophers”,
“name” : “Three Philosophers”
}

So to ensure that all of or model objects have this ID property we’ll create a model base class that defines one.

[Serializable]
public abstract class ModelBase
{
[JsonProperty(PropertyName = “_id”)]
public string Id { get; set; }
}

I won’t dig into he JsonProperty pieces here.  I’m using Json.NET for serialization and wrote more about it at http://www.couchbase.com/develop/net/next.  The important thing to note is that we have model objects with an “Id” property and by constraining our repositories to use subclasses of ModelBase, we know we’ll always have this property.

When creating views, it’s useful to have a taxonomy on documents so that it’s easy to find all “beer” documents or all “user” documents or even to find a “user” document with a given email address.  To facilitate this requirement, we’ll impose a “type” property on all model documents.  To our ModelBase, we’ll add a readonly, abstract Type property.

[JsonProperty(“type”)]
public abstract string Type { get; }

Now that we understand the assumptions we can make of our model, let’s return to our RespositoryBase class.

The first thing our RepositoryBase class needs to do is define an instance of the CouchbaseCliet.

protected static readonly CouchbaseClient _Client = null;

static RepositoryBase()
{
_Client = new CouchbaseClient();
}

Since client setup is expensive, we’re going to ensure it happens only once per AppDomain by creating a readonly static instance that’s instantiated in a static constructor.  The client is exposed as a protected field to subclasses who need to extend the basic CRUD methods in our base class.  Note that this client depends on the Web.config having defined the “couchbase” section.  At some point, I’ll add a IoC friendly version of our RepositoryBase.

The repository base defines Create, Update and Save methods.  These methods in turn call the private “store” method with the appropriate StoreMode value.  Create and Update are straightforward.  Save simply creates a document when the key doesn’t exist and replaces it when it does.  The return values are the CAS value for the operation.  At some point, I’ll add CAS as an optional parameter.  The store method also calls a CasJson extension method that serializes all objects to JSON strings before saving them to Couchbase.

public virtual ulong Create(T model)
{
return store(StoreMode.Add, model);
}

public virtual ulong Update(T model)
{
return store(StoreMode.Replace, model);
}

public virtual ulong Save(T model)
{
return store(StoreMode.Set, model);
}

private ulong store(StoreMode mode, T model)
{
var result = _Client.CasJson(mode, BuildKey(model), model);
return result.Result ? result.Cas : 0;
}

The private store method also makes use of the protected BuildKey method to create keys by a convention.  Specifically, keys are composed from the Type property of the model and its Id property, with spaces replaced by underscores.  TapMap isn’t actually making use of this version of BuildKey as it’s overridden in subclasses that use Create, Update or Save.  I’m considering adding a “Name” property to the ModelBase to allow for composing a key based on a type and name.  For example, a beer with a name “Old Yankee Ale” gets a key “beer_Old_Yankee_Ale.”

protected virtual string BuildKey(T model)
{
return string.Concat(model.Type, “_”, model.Id.Replace(” “, “_”));
}

The Get method takes the _id of the document as an argument when called.  That value is then used as the key for calling the client’s generic Get method.  Another extension method – GetJson(string id) – is also being used by the Get method to convert the JSON document to an instance of T.

public virtual T Get(string id)
{
var doc = _Client.GetJson<T>(id);
if (doc != null) doc.Id = id; //server doesn’t pass back the _id in the JSON
return doc;
}

Remove is very straightforward.  It simply takes the document key (again, also the _id) and removes the item from Couchbase.

public virtual void Remove(string id)
{
_Client.Remove(id);
}

Providing base support for view access is done by convention in the View method.  This method assumes that the design document name is the model type name pluralized.  So BeerRepository will automatically have its view requests mapped to a design doc named “beers.”  The View method returns an IView instance.  Since the HTTP call isn’t actually made until a caller iterates over the IView, subclasses can call on the view using the fluent query methods.  I’ll have an example of a specific view call shortly.

protected IView<IViewRow> View(string viewName)
{
return _Client.GetView(InflectorNet.Pluralize(typeof(T).Name.ToLower()), viewName);
}

There’s also currently a SpatialView method, which allows for geospatial queries using GeoCouch.  The GetSpatialView method of the client is currently experimental and hasn’t been added to the client’s 1.2 master branch.  It’ll be forthcoming however.  If you clone the TapMap repository, you’ll get a client binary with support for this method.

protected IView<ISpatialViewRow> SpatialView(string viewName)
{
return _Client.GetSpatialView(InflectorNet.Pluralize(typeof(T).Name.ToLower()), viewName);
}

That covers the RepositoryBase.  So now that we’ve seen its methods and purpose, let’s dig into a subclass.  First, the User model class defines properties for Username, Email and Password.  It also provides JSON property mappings to lowercased versions of the class properties.  Notice also that User extends ModelBase and implements the Type property by returning the string “user.”

[Serializable]
public class User : ModelBase
{
[Required]
[JsonProperty(“username”)]
public string Username { get; set; }

[Required]
[JsonProperty(“email”)]
public string Email { get; set; }

[Required]
[JsonProperty(“password”)]
public string Password { get; set; }

public override string Type
{
get { return “user”; }
}
}

The UserRepository class extends RepositoryBase with T being set to type User.

public class UserRepository : RepositoryBase<User>
{
//methods
}

With the simple definition above, the UserRepository has quite a bit of power.  It can perform all of the basic CRUD functionality defined in its base class.  However, there are some special scenarios for working with User documents in the app and the UserRepository adds methods to support these scenarios.

When a user logs in with a username and password, one option would be to create a view that emits a composite key of username and password.  However, a better alternative is to use the username and password to compose a key for the document.  Such a key will allow us to retrieve a user document easily when the user logs in.  This approach saves space by not having to create the secondary view index and allows for faster key by using the hashtable lookup.

In the overridden Create method, the username (Id) and password (hashed) are used to create a key of the form user_Username_HashedPassword.

public override ulong Create(User model)
{
model.Password = HashHelper.ToHashedString(model.Password);
model.Id = BuildKey(model);
var result = _Client.CasJson(StoreMode.Add, BuildKey(model), model);
return result.Result ? result.Cas : 0;
}

BuildKey is also overridden to allow for the new key format.   The buildKey private method is needed by the overloaded Get method that we’ll see shortly.

protected override string BuildKey(User user)
{
return buildKey(user.Username, user.Password);
}

private string buildKey(string username, string password)
{
return string.Concat(“user_”, username, “_”, password);
}

The Get method as defined in RepositoryBase assumes a lookup by key.  Rather than expose the special user key format to the calling UserController class, Get is overloaded to accept a username and password from which the new key is composed.

public User Get(string username, string password)
{
return Get(buildKey(username, HashHelper.ToHashedString(password)));
}

The UserRepository class is also responsible for helping to answer the questions of whether a username or email is unique.  To so do, two views are used.  The first simply emits email as key for all user documents; the second emits the username as key.  There are more clever ways to create these indexes, but to keep things simple, there’s a one-to-one mapping between the property to be checked and its view.

function (doc) {
if (doc.type == “user”) {
emit(doc.email, null);
}
}

function (doc) {
if (doc.type == “user”) {
emit(doc.usrename, null);
}
}

The UserRepository defines GetByEmail and GetByUsername methods, which both use the base View method to query their respective views.  In both methods, the limit is set to 1 and there’s a hard return on the first iteration since we care only that a single item exists with this property value.  Stale views are not allowed, because we are trying to enforce uniqueness of data and we need to make sure we have the most up to date view index possible.

public User GetByEmail(string email)
{
foreach (var item in View(“by_email”).Limit(1).Key(email).Stale(StaleMode.False))
{
return Get(item.ItemId);
}
return null;
}

public User GetByUsername(string username)
{
foreach (var item in View(“by_username”).Limit(1).Key(username).Stale(StaleMode.False))
{
return Get(item.ItemId);
}
return null;
}

I’ll explore the UserController at another time to show how it all fits together.  My goal with this post was to introduce the RepositoryBase and how it’s extended.

Author

Posted by John Zablocki, NET. SDK Developer, Couchbase

John Zablocki is a NET. SDK Developer at Couchbase. John is also the organizer of Beantown ALT.NET and a former adjunct at Fairfield University. You can also check out the book on Amazon named "Couchbase Essentials" which explains how to install and configure Couchbase Server.

Leave a reply