Exploring MongoDB with F#
If you’ve been following me on Twitter, I’ve been digging a bit into MongoDB. When I was involved with the planning of NoSQLEast this past year, I sat down and used it in anger and was quite pleased with the results. Using it with a language which allows for quick prototyping such as F# has afforded me to get up and going on a project with very little effort. At some point, I don’t want to be bothered with having to go into another tool, create a schema, decide what data types, run migrations and all the fun things that come along with traditional RDBMS solutions. I just want a quick answer with the data I have. There was one issue of course that nagged me which was the ubiquitous use of strings for everything from databases, collections, and keys. With a language such as F#, could we do any better than this approach?
From Dictionaries
Before we look at any potential solution, let’s first look at how we insert a document using the normal string approach with the C# MongoDB Driver. Let’s take books for a potential book reviewing system as an example. Let’s first insert a document into the collection.
open MongoDB.Driver let mongo = new Mongo() if not <| mongo.Connect() then failwith "Could not connect to MongoDB instance" let books = mongo.["BookReviews"].["Books"] // Insert a book let book1 = new Document() book1.["Title"] <- "Programming F#" book1.["Authors"] <- ["Chris Smith"] book1.["Publisher"] <- "O'Reilly" book1["Rating"] <- 5.0 books.Insert(book1)
In the code above, I connected to the MongoDB instance I have running locally. After that, I browse to the BookReviews database and then to the Books collection. What’s interesting about that last piece is that if it doesn’t exist already, MongoDB will create it for me, so that saved me quite a bit from having to go into another tool, create a database, define columns, set indexes, etc. After I get the Books collection, I then create a new Document with some keys and insert it into the collection. Now, how about querying? Let’s say I want to get all books by O’Reilly publishing. How would I do it?
let oreillyQuery = new Document() query.["Publisher"] <- "O'Reilly" let oReillyBooks = books.Find(oReillyQuery) printfn "O'Reilly books %d" Seq.length oReillyBooks.Documents if not <| mongo.Disconnect() then failwith "Could not disconnect from MongoDB instance"
As you can see from the code above, we simply need to create a query document and set the appropriate keys. Then we use the books collection to find it based upon our query. Overall, it’s a great experience, but that’s an awful lot of string usage, more than my taste usually allows. What can we do about it?
To Dynamic
As we covered in the previous post, F# has added support for both a dynamic get ( ? ) and set operator ( ?<-). Note, that I said support and not actual implementation. The actual implementation is up to you and your need. There are a couple of solutions that we could explore on making our lives easier with this operator. Ultimately, our goal is to look something like the following:
let mongo = new Mongo() if not <| mongo.Connect() then failwith "Could not connect to MongoDB instance" let books = mongo.["BookReviews"].["Books"] let book = new Document() book?Title <- "Real World Haskell" book?Authors <- ["Bryan O'Sullivan"; "John Gorezen"; "Don Stewart"] book?Publisher <- "O'Reilly" book?Rating <- 4.6 books.Insert(book) if not <| mongo.Disconnect() then failwith "Could not disconnect from MongoDB instance"
Or better yet, rid ourselves of the strings altogether such as BookReviews and Books. One solution could be to use the incoming type in the ? operator to be an IDictionary as the MongoDB C# Driver Document class is based off an IDictionary.
open System.Collections let (?) (this : IDictionary) key = this.[key] let (?<-) (this : IDictionary) key value = this.[key] <- value
This will allow for our calls to the document to be just as I wanted in the beginning. But, that doesn’t cover how I could possibly cover the case for getting our database and our collection. One common thread between the two of them is the indexed property of Item. With that in hand, we could revisit the idea of using member restrictions to model this dynamic lookup of both gets and sets. Let’s look at the code required for setting these member restrictions.
let inline (?) this key = ( ^a : (member get_Item : ^b -> ^c) (this,key)) let inline (?<-) this key value = ( ^a : (member set_Item : ^b * ^c -> ^d) (this,key,value))
This gives us not only the ability to deal with our Documents much as we had above, but also change our calls to get our database and collection, such as:
// Old let books = mongo.["BookReviews"].["Books"] // New let books = mongo?BookReviews?Books
But, ultimately, as I pointed out in the last post, this has potential problems due to the nature of how F# treats the get_Item and set_Item properties. For example, this solution does not work quite well with LINQ, such as the following will return an error.
let d = [dict [("dynamic", 123)]; dict [("dynamic", 456)]] let allDynamics = query <@ seq { for m in d.AsQueryable() do yield m?dynamic } @> val alllDynamics : seq<int> = Error: Specified method is not supported.
The previous example of using IDictionary/IDictionary<’Key,’Value>, however, does work in this scenario. What about the DynamicObject approach? As Scott Watermasysk described in his post on MongoDB and C# Dynamics it certainly is possible for us to take advantage of a DynamicObject with TryGetMember/TrySetMember defined. But, how in F# could we take advantage of that? I hope to cover that soon enough.
Conclusion
MongoDB has been a great source of inspiration for me lately as I look at Not Only SQL solutions. Not having to worry about migrations, schemas, data types and the likes, I’ve been able to be quite productive in prototyping ideas, and getting answers quickly. In addition, with a language such as F# where we can instantly get feedback through our REPL, it adds another dimension to trying out new solutions.
With the flexibility of the F# language, we’re able to get some sort of syntactic sugar which allows us to make MongoDB look and feel much like we’re in LINQ to SQL without even the need for the dynamic bindings that are coming in .NET 4.0.