On Sequences with MongoDB and NoRM

One of the first things I noticed when I started to move some code from NHibernate/SQL Server to NoRM/MongoDB is that ObjectIds look funny in MVC URLs. In other words:

http://codevoyeur.com/articles/show/bd342503dbbd3c94e1010000 vs. http://codevoyeur.com/articles/show/10

NoRM’s overloaded operators let ObjectIds and strings play nicely together so there’s no real problem with MVC routing. But for many people (myself included) there’s something more aesthetically pleasing about a short int ID in a URL than a long ObjectID string.

A pattern I’ve used (is it a pattern if I’ve done it twice?) is to add a unique “FriendlyId” field to my collections that will have IDs in URLs. The values for the FriendlyId come from a Sequences collection that I’ll describe below. It might be redundant to include second unique ID. It’s possible simply to change the _id field to be “friendly.” But there’s some value in keeping the ObjectID. Namely, it’s provides a built-in timestamp for your documents. Either way though, it doesn’t affect the general I’ve taken.

If you’ve used Oracle, you’re probably familiar with the fact that Oracle doesn’t support auto-increment identity columns (or at least it didn’t when I last used it). Instead, you create a sequence and relate it to a table by name only. In other words, you have a table PRODUCTS and a sequence SEQ_PRODUCTS. When you insert into the PRODUCTS table, you increment the current value in the sequence and use that incremented value as the PK value for your insert.

INSERT INTO Products
(ProductID, Name)
VALUES
(SEQ_PRODUCTS.NEXTVAL, "Fender Telecaster")

To implement a similar feature using NoRM and MongoDB, start by creating a Sequence class:

public class Sequence {

    public ObjectId Id { get; set; }
    public string Name { get; set; }
    public int Value { get; set; }

}

The name could/should arguably be the ObjectId. But as I stated above, I have a certain fondness for keeping the default ObjectId around. I’m sure I’ll let go of that bias when I concede it’s just extra information being stored unnecessarily. Anyway, Sequence is just a simple class to associate a sequence name to the current int value of that sequence. So the documents would look like:

{ _id : "30e8a403af6bd93045000000", Name : "Products", Value : 101 },
{ _id : "30e8a405af6bd93045000021", Name : "Categories", Value : 55 }

To manage the incrementing and retrieval of the sequences, I’ve created a simple SequenceRepository class (I’ve omitted some plumbing for brevity).

public class SequenceRepository {

    private object _sequenceLock = new Object();

    public int GetNextValue(string name) {

        Sequence sequence = null;
        lock (_sequenceLock) {
            using (Mongo mongo = Mongo.Create("mongodb://localhost/CodeVoyeur")) {
                var coll = mongo.Database.GetCollection("Sequences");
                sequence = coll.FindOne(new { Name = name });

                if (sequence == null) {
                    sequence = new Sequence() { Name = name };
                }

                sequence.Value += 1;
                coll.Save(sequence);
                return sequence.Value;
            }
        }
    }
}

The GetNextValue method simply checks whether the sequence exists. If not, it creates it. The value is then incremented and saved.

An simple ProductRepository could then make use of the sequence as follows:

public class ProductRepository {

    public void Create(Product product) {
        using (Mongo mongo = Mongo.Create("mongodb://localhost/CodeVoyeur")) {

            product.FriendlyId =
                        new SequenceRepository().GetNextValue("Products");
            mongo.Database.GetCollection("Products")
                        .Insert(product);
        }
    }
}

Of course, it’s not great for repositories to have the dependency on the SequenceRepository. A more complete implementation would have some sort of RepositoryBase class that might encapsulate this functionality (instead of having a SequenceRepository). Or some other layer might coordinate the efforts of the two repositories.

I’m not suggesting this approach for all collections. There’s obviously some overhead with the addition of a new connection plus a query and save for each insert. But I’m assuming the use case for this pattern is not tracking millions of ad impressions, but rather thousands of Products or dozens of blog posts.

This entry was posted in Uncategorized. Bookmark the permalink.

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>