Implementing Polyglot Persistence – Part 3
In this post I will start creating a Mongolab repository to perform CRUD operations against the MongoDB database I created in Part 2. As a starting point, I will use the solution that I introduced in Part 1, adding a new repository for Mongolab.
Get the MongoDB Client Library
To start, I’ll need some client libraries to program against my MongoDB. I’ll be using the C# drivers that 10gen supports, which can be found here. This is an outstanding resource for documentation and samples on how to use the drivers.
Using the “Manage Nuget Packages…” feature in Visual Studio 2012 and searching for “mongocsharpdriver” makes installing the drivers for my Mongolab project a one-click task.
Handling Multiple Product Schemas
MongoDB is a “schemaless” database. While the database may not be enforcing schema, it’s still helpful to have some basic schema in place (in the application). Recall from Part 1 that I created a very basic model (schema) for products and reviews in ProductService.Model. However, in Part 2 where I created my database, I was not restricted to the model and MongoDB was perfectly happy to take whatever I gave it.
In MongoDB, you can reference the collection as a generic collection of BsonDocument types. Some examples of how to use this document type are in the C# Driver Tutorial. This exhibits a great deal of flexibility when working with the collection. With that flexibility, however, comes challenges in how you’re able to query the collection. For example, if you’re not able to assume any model (or schema) at all, then field names for the entities obviously can be different or non-existent. Even the casing of field names starts to become an issue (“Price” does not equal “price”).
Having a basic model for products and insuring items in the collection adhere to that basic model I’ve found makes things much simpler. Yet, I still have the power of schema flexibility in the database. To handle the various properties that a product might have, I extended my ProductService.Model.Product to include a new property called “ExtraElements”. I extended this internal to my repository since this is a capability the client library supports and not relevant to other potential repository implementations using the model.
Now, anytime I refer to the “products” collection in my database, I do so using this new ProductDocument class as the type. I wrote a little helper class to return an instance of the collection for me using this type.
Because I have two classes representing products (Product and ProductDocument), I need to give some hints to the client library so it can properly serialize these. I also need to tell it how to handle Id’s (more on that later). This is accomplished using the BsonClassMap, as shown here.
In the first class map, I’m telling it that when serializing a product as a ProductService.Model.Product, ignore any extra properties that may exist for the product. I’m also telling it that the “Id” field should be represented as an ObjectId.
In the second class map, I’m telling it that when serializing a product as a ProductService.Repository.Mongolab.ProductDocument, store any extra data it finds for the product in the “ExtraElements” property I added above.
The Id Field
When I inserted the JSON documents into my products collection, a “_id” field was created automatically for each product. I could have generated the field myself and populated it differently, but I chose to just let the database do it for me when I added these documents in Part 2.
The “_id” field is actually an ObjectId, which is a structure of 12 bytes (no less and no more) and not to be confused for a Guid (although similar). Its value takes into account things like time, process id, machine and an incrementing counter.
My ProductService.Model.Product already has an “Id” field and the client library automatically maps any field named “Id” to the “_id” field in the database. So, my “Id” is already handled and queries for products by Id will just work.
Implementing the Repository
With everything described above in place, the implementation of the repository methods is very simple. Here I’ll show a few of the methods and the output using Fiddler to generate requests.
The GetProduct Implementation
This method returns the base Product representation of a product. Recall from above that my collection is actually a collection of ProductDocument. Using the FindOneByIdAs<> method, I can tell Mongo to return the document as a Product instance (ie: no product details).
The GetProductDetails Implementation
This will return the product and any additional fields stored for the product.
One side effect of this is the “Extra Elements” field wrapping the additional product details. Ideally, I’d like for the additional product details to appear as they do in my MongoDB without being wrapped by the “Extra Elements” field. Perhaps in a future post I’ll fix this with some custom serialization. For now, I’m happy to accept this little nuance.
The GetCategories Implementation
The use case for this method is one where you need a list of all available categories of products in the collection. For example, to populate a list of categories on a web page so users can browse products by category.
I’ll get to the rest of the methods in the repository in Part 4. Things get a little more interesting when implementing search. Although, as you can probably imagine, the client libraries simplify this considerably.
Things I didn’t cover here are how settings for my database are stored in the web.config file. Those are fairly trivial details though. My solution is available here for anyone interested in seeing the full implementation. To use my solution, just replace these settings in web.config with your own.