Typed Hack of Cloudant using F#

Learn how to bridge typed F# with untyped Cloudant NoSQL databases. Discover a practical helper module for seamless data access in under 100 lines.

5 min read 803 words

There is a continuous war between typed and untyped data. But then there is a need for finding a middle ground for ever-growing applications. There is a place, a requirement when I feel an application should be typed and the data store should be untyped. So, I created this little code snippet to access Cloudant (a CouchBase fork) NoSQL database.

I have known about Cloudant from long back. Even before IBM bought it. It is indeed a wonderful service and also kind of free for small stuff. I thought this would be the best fit for now. (I still don't know why I didn't use MongoDB).

As there is no need for scary relationships with the database, I decided to use this one.

Now, as one problem is solved, another started. It is saving data that is in JSON format. That is untyped, and F# is statically typed. I have tried a few libraries developed in C# but was not happy. So, I thought it is just an HTTP request—why shouldn't I give it a shot to make one of my own helper modules? It may be somewhat of a dirty attempt, but I got it working with what I needed in less than 100 lines. Here are the code snippets.

#r "../packages/Http.fs.1.4.0/lib/net40/HttpClient.dll"
#r "../packages/Newtonsoft.Json.6.0.6/lib/net45/Newtonsoft.Json.dll"

open HttpClient
open Newtonsoft.Json
open Newtonsoft.Json.Linq

[<CLIMutableAttribute>]
type Row<'a> = 
    { id : string
      key : string
      value : 'a }

[<CLIMutableAttribute>]
type ResultSet<'a> = 
    { total_rows : int
      offset : int
      rows : Row<'a> [] }

[<CLIMutableAttribute>]
type PostResult = 
    { ok : string
      id : string
      rev : string }

[<CLIMutableAttribute>]
type PostError = 
    { error : string
      reason : string }

let cloudantUrl = @"<cloudanturl/databasename/>"
let username = @"<username>"
let password = @"<password>"

let private cloudantGet url = 
    let request = 
        createRequest Get url
        |> withBasicAuthentication username password
        |> withHeader (ContentType "application/json")
    request |> getResponseBody

let private cloudantPost url data = 
    let request = 
        createRequest Post url
        |> withBasicAuthentication username password
        |> withBody data
        |> withHeader (ContentType "application/json")
    request |> getResponseBody

let private checkDataForNewId (data : JObject) = 
    let removeIdrev (data : JObject) = 
        data.Remove("_id") |> ignore
        data.Remove("_rev") |> ignore
    if System.String.IsNullOrEmpty(data.["_id"].ToString()) || System.String.IsNullOrEmpty(data.["_rev"].ToString()) then 
        removeIdrev data
    data

let PostJson<'a> data = 
    let serializedObject = JObject.FromObject(data) |> checkDataForNewId
    serializedObject.Add("$doctype", JToken.Parse("'" + data.GetType().Name + "'"))
    cloudantPost cloudantUrl <| serializedObject.ToString()

let GetJsonByType<'a> = 
    let resultset = 
        JsonConvert.DeserializeObject<ResultSet<'a>>
            (cloudantGet (cloudantUrl + "/_design/Type/_view/" + typeof<'a>.Name))
    query { 
        for row in resultset.rows do
            select row.value
    }

let GetJsonById<'a> Id = JsonConvert.DeserializeObject<'a>(cloudantGet (cloudantUrl + Id))

[<CLIMutableAttribute>]
type Person = 
    { _id : string
      _rev : string
      FirstName : string
      LastName : string }

let newPerson = 
    { _id = ""
      _rev = ""
      FirstName = "Boom"
      LastName = "Baam" }

let inline isNull (x:^a when ^a : not struct) =
    obj.ReferenceEquals (x, Unchecked.defaultof<_>)

let findPerson = 
    query { 
        for p in GetJsonByType<Person> do
            where (p.FirstName = "Boom")
            select p
            headOrDefault
            }
isNull findPerson    
GetJsonById<Person> ("3b389dc6b8ee0dcbf7f366faaa59cf42")

The above code below this part is just for testing. And even with that, the code snippet is 98 lines. So, with blank lines removed, it is even shorter.

Now, in Cloudant I need to create views, so the code is like:

function(doc) {
    if (doc.$doctype !== "Person") return;
    var copydoc = JSON.parse(JSON.stringify(doc));
    delete copydoc["$doctype"];
    emit(doc._id,copydoc);
}

I need $doctype while I am inserting or updating data only, not while reading. So, I am removing it since I am already filtering based on type.

In the above code, two libraries are being used: one is Http.fs and the other is JSON.net.

In the library, as you can see, I am not doing anything special. I am inserting data with type information. If new data is there, I am removing _id and _rev, and for simplicity, I have put _id and _rev in all the types I am using to interact with Cloudant data store.

So, now whenever I am reading, I just need to give the type, and that will be fetched as a collection of that typed record. Once the collection comes to memory, I have the powerful F# to process data. In the above code, I am using a query expression to do so. It becomes very easy and fun to use. And it is damn fast. At least for now.

Now, I don't know if it is perfect or not. It is kind of part functional and part object-oriented, as far as I know. Should I convert this to a Type Provider, or is it possible or not? I don't know. But one thing is sure: I am achieving what I wanted with the simplest possible code.

Dear F# community members, do provide your views on this. And also let me know if it will be useful to make it more mature and push it as a NuGet package. Or maybe a Type Provider for Cloudant?

Highly inspired by Daniel Mohl's old library FSharpCouch. Thank you... :)

Frequently Asked Questions

Why use F# with Cloudant instead of an existing library?

The author found existing C# libraries unsatisfactory for their needs. Since Cloudant is accessed via HTTP requests, they created a custom helper module in under 100 lines of F# code that perfectly fit their requirements without unnecessary overhead.

How does F# handle untyped JSON data from Cloudant?

F# uses type definitions with CLIMutable attributes to map untyped JSON responses into strongly-typed records. This approach bridges the gap between Cloudant's untyped JSON format and F#'s static typing system, providing type safety while working with NoSQL data.

What authentication method does this Cloudant implementation use?

The code uses basic authentication with username and password credentials. These credentials are applied to each HTTP request to Cloudant via the withBasicAuthentication function, securing access to your database.

When should you choose an untyped database with a typed language?

This approach works well when your application doesn't require complex relationships between data entities, making a NoSQL database suitable. It's ideal for scenarios where you want the flexibility of NoSQL while maintaining type safety in your application code.

How does the PostJson function handle new versus existing records?

The checkDataForNewId function removes internal Cloudant fields (_id and _rev) if they're empty, indicating a new record. This ensures proper handling of both new document creation and updates to existing documents in Cloudant.

Share this article